详细解读Angular系列中变化检测问题

这篇文章主要介绍了详解angular系列之变化检测(change detection),现在分享给大家,也给大家做个参考。

概述

简单来说变化检测就是Angular用来检测视图与模型之间绑定的值是否发生了改变,当检测到模型中绑定的值发生改变时,则同步到视图上,反之,当检测到视图上绑定的值发生改变时,则回调对应的绑定函数。

什么情况下会引起变化检测?

总结起来, 主要有如下几种情况可能也改变数据:

用户输入操作,比如点击,提交等

请求服务端数据(XHR)

定时事件,比如setTimeout,setInterval

上述三种情况都有一个共同点,即这些导致绑定值发生改变的事件都是异步发生的。如果这些异步的事件在发生时能够通知到Angular框架,那么Angular框架就能及时的检测到变化。

详细解读Angular系列中变化检测问题

左边表示将要运行的代码,这里的stack表示Javascript的运行栈,而webApi则是浏览器中提供的一些Javascript的API,TaskQueue表示Javascript中任务队列,因为Javascript是单线程的,异步任务在任务队列中执行。

具体来说,异步执行的运行机制如下:

所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之 中放置一个事件。

一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

主线程不断重复上面的第三步。

当上述代码在Javascript中执行时,首先func1 进入运行栈,func1执行完毕后,setTimeout进入运行栈,执行setTimeout过程中将回调函数cb 加入到任务队列,然后setTimeout出栈,接着执行func2函数,func2函数执行完毕时,运行栈为空,接着任务队列中cb 进入运行栈得到执行。可以看出异步任务首先会进入任务队列,当运行栈中的同步任务都执行完毕时,异步任务进入运行栈得到执行。如果这些异步的任务执行前与执行后能提供一些钩子函数,通过这些钩子函数,Angular便能获知异步任务的执行。

angular2 获取变化通知

那么问题来了,angular2是如何知道数据发生了改变?又是如何知道需要修改DOM的位置,准确的最小范围的修改DOM呢?没错,尽可能小的范围修改DOM,因为操作DOM对于性能来说可是一件奢侈品。

在AngularJS中是由代码$scope.$apply()或者$scope.$digest触发,而Angular接入了ZoneJS,由它监听了Angular所有的异步事件。

ZoneJS是怎么做到的呢?

实际上Zone有一个叫猴子补丁的东西。在Zone.js运行时,就会为这些异步事件做一层代理包裹,也就是说Zone.js运行后,调用setTimeout、addEventListener等浏览器异步事件时,不再是调用原生的方法,而是被猴子补丁包装过后的代理方法。代理里setup了钩子函数, 通过这些钩子函数, 可以方便的进入异步任务执行的上下文.

//以下是Zone.js启动时执行逻辑的抽象代码片段function zoneAwareAddEventListener() {...}function zoneAwareRemoveEventListener() {...}function zoneAwarePromise() {...}function patchTimeout() {...}window.prototype.addEventListener=zoneAwareAddEventListener;window.prototype.removeEventListener=zoneAwareRemoveEventListener;window.prototype.promise = zoneAwarePromise;window.prototype.setTimeout = patchTimeout;

登录后复制

变化检测的过程

Angular的核心是组件化,组件的嵌套会使得最终形成一棵组件树。Angular的变化检测可以分组件进行,每一个Component都对应有一个changeDetector,我们可以在Component中通过依赖注入来获取到changeDetector。而我们的多个Component是一个树状结构的组织,由于一个Component对应一个changeDetector,那么changeDetector之间同样是一个树状结构的组织.

另外,Angular的数据流是自顶而下,从父组件到子组件单向流动。单向数据流向保证了高效、可预测的变化检测。尽管检查了父组件之后,子组件可能会改变父组件的数据使得父组件需要再次被检查,这是不被推荐的数据处理方式。在开发模式下,Angular会进行二次检查,如果出现上述情况,二次检查就会报错:Expression Changed After It Has Been Checked Error。而在生产环境中,脏检查只会执行一次。

相比之下,AngularJS采用的是双向数据流,错综复杂的数据流使得它不得不多次检查,使得数据最终趋向稳定。理论上,数据可能永远不稳定。AngularJS给出的策略是,脏检查超过10次,就认为程序有问题,不再进行检查。

详细解读Angular系列中变化检测问题

变化检测策略

Angular有两种变化检测策略。Default是Angular默认的变化检测策略,也就是上述提到的脏检查,只要有值发生变化,就全部从父组件到所有子组件进行检查,。另一种更加高效的变化检测方式:OnPush。OnPush策略,就是只有当输入数据(即@Input)的引用发生变化或者有事件触发时,组件才进行变化检测。

defalut 策略

main.component.ts

@Component({ selector: 'app-root', template: ` 

变更检测策略

 

{{ slogan }}

   `,})export class AppComponent { slogan: string = 'change detection'; title: string = 'default 策略'; star: Star = new Star('周', '杰伦'); changeStar() {  this.star.firstName = '吴';  this.star.lastName = '彦祖'; } changeStarObject() {  this.star = new Star('刘', '德华'); } }

登录后复制

movie.component.ts

@Component({ selector: 'movie', styles: ['p {border: 1px solid black}'], template: `

{{ title }}

{{star.firstName}} {{star.lastName}}

`,})export class MovieComponent { @Input() title: string; @Input() star;}

登录后复制

上面代码中, 当点击第一个按钮改变明星属性时,依次对slogan, title, star三个属性进行检测, 此时三个属性都没有变化, star没有发生变化,是因为实质上在对star检测时只检测star本身的引用值是否发生了改变,改变star的属性值并未改变star本身的引用,因此是没有发生变化。

而当我们点击第二个按钮改变明星对象时 ,重新new了一个 star ,这时变化检测才会检测到 star发生了改变。

然后变化检测进入到子组件中,检测到star.firstName和star.lastName发生了变化, 然后更新视图.

OnPush策略

与上面代码相比, 只在movie.component.ts中的@component中增加了一行代码:

changeDetection:ChangeDetectionStrategy.OnPush
此时, 当点击第一个按钮时, 检测到star没有发生变化, ok,变化检测到此结束, 不会进入到子组件中, 视图不会发生变化.

当点击第二个按钮时,检测到star发生了变化, 然后变化检测进入到子组件中,检测到star.firstName和star.lastName发生了变化, 然后更新视图.

所以,当你使用了OnPush检测机制时,在修改一个绑定值的属性时,要确保同时修改到了绑定值本身的引用。但是每次需要改变属性值的时候去new一个新的对象会很麻烦,immutable.js 你值得拥有!

变化检测对象引用

通过引用变化检测对象ChangeDetectorRef,可以手动去操作变化检测。我们可以在组件中的通过依赖注入的方式来获取该对象:

constructor(  private changeRef:ChangeDetectorRef ){}

登录后复制

变化检测对象提供的方法有以下几种:

markForCheck() – 在组件的 metadata 中如果设置了 changeDetection:ChangeDetectionStrategy.OnPush 条件,那么变化检测不会再次执行,除非手动调用该方法, 该方法的意思是在变化监测时必须检测该组件。

detach() – 从变化检测树中分离变化检测器,该组件的变化检测器将不再执行变化检测,除非手动调用 reattach() 方法。

reattach() – 重新添加已分离的变化检测器,使得该组件及其子组件都能执行变化检测

detectChanges() – 从该组件到各个子组件执行一次变化检测

OnPush策略下手动发起变化检测

组件中添加事件改变输入属性

在上面代码movie.component.ts中修改如下

@Component({ selector: 'movie', styles: ['p {border: 1px solid black}'], template: `

{{ title }}

    {{star.firstName}} {{star.lastName}}

`,changeDetection:ChangeDetectionStrategy.OnPush})export class MovieComponent { constructor(  private changeRef:ChangeDetectorRef ){} @Input() title: string; @Input() star;  changeStar(){  this.star.lastName = 'xjl'; }}

登录后复制

此时点击按钮切换名字时,star更改如下

![图片描述][3]

第二种就是上面讲到的使用变化检测对象中的 markForCheck()方法.

ngOnInit() {  setInterval(() => {   this.star.lastName = 'xjl';   this.changeRef.markForCheck();  }, 1000); }

登录后复制

输入属性为Observable

修改app.component.ts

@Component({ selector: 'app-root', template: ` 

变更检测策略

 

{{ slogan }}

   `,})export class AppComponent implements OnInit{ slogan: string = 'change detection'; title: string = 'OnPush 策略'; star: Star = new Star('周', '杰伦'); count:Observable; ngOnInit(){  this.count = Observable.timer(0, 1000) } changeStar() {  this.star.firstName = '吴';  this.star.lastName = '彦祖'; } changeStarObject() {  this.star = new Star('刘', '德华'); } }

登录后复制

此时,有两种方式让MovieComponent进入检测,一种是使用变化检测对象中的 markForCheck()方法.

ngOnInit() {  this.addCount.subscribe(() => {   this.count++;   this.changeRef.markForCheck();  })

登录后复制

另外一种是使用async pipe 管道

@Component({ selector: 'movie', styles: ['p {border: 1px solid black}'], template: `

{{ title }}

    {{star.firstName}} {{star.lastName}}

{{addCount | async}}

`, changeDetection: ChangeDetectionStrategy.OnPush})

登录后复制

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

利用vue实现如何在表格里,取每行的id(详细教程)

利用vue移动端UI框架如何实现QQ侧边菜单(详细教程)

使用vue及element组件的安装教程(详细教程)

以上就是详细解读Angular系列中变化检测问题的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2749929.html

(0)
上一篇 2025年3月8日 05:27:46
下一篇 2025年3月8日 05:27:56

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 在Angular中如何实现双向数据绑定

    下面小编就为大家分享一篇angular自定义组件实现数据双向数据绑定的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 学过Angular的同学都知道,输入框通过[(ngModel)]实现双向数据绑定,那么自定义组件能不…

    2025年3月8日
    200
  • 在Angular中如何实现颜色变化

    这篇文章主要介绍了angular中点击li标签实现更改颜色的核心代码,需要的朋友可以参考下 点击ng-repeat遍历后的li标签,实现更改border颜色; html代码: 登录后复制 {{word}} js代码: $scope.li_c…

    编程技术 2025年3月8日
    200
  • 在Angular中如何使用toDoList

    本篇文章主要介绍了angular之todolist的实现代码示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 什么是todolist? 所谓的todolist就是把你所做的事情按顺序全部列出来,然后做完一件事…

    2025年3月8日
    200
  • 使用vue,angular,react如何实现数据双向绑定

    本篇文章主要介绍了浅谈vue,angular,react数据双向绑定原理分析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 传统做法 前端维护状态,手动操作DOM更新视图。前端框架对服务器数据通过模版进行渲染。…

    2025年3月8日
    100
  • 关于Angular4 中内置指令的基本用法

    下面这篇文章主要给大家介绍了关于angular4中内置指令的基本用法,文中通过示例代码介绍的非常详细,需要的朋友们下面来一起看看吧。 不得不说指令是ng最为强大的功能之一,好吧,也可以去掉之一,是最强大的功能。 前言 大家都知道ng内置了许…

    编程技术 2025年3月8日
    200
  • Angular中使用better-scroll插件的方法介绍

    本篇文章主要介绍了angular中使用better-scroll插件的方法,内容挺不错的,现在分享给大家,也给大家做个参考。 better-scroll的使用 由于需要在一个固定的的高度做无限滚动,本来css的overflow-y也可以完成…

    编程技术 2025年3月8日
    200
  • 关于Angularjs中的$apply及优化使用

    这篇文章主要给大家介绍了关于angularjs中$apply及优化使用的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧 前言 对于一个在前端属于纯新手的我来说,Javascript都还是一知半解,要想直接…

    编程技术 2025年3月8日
    200
  • Angular HMR(热模块替换)功能的实现方法

    本篇文章主要介绍了angular hmr(热模块替换)功能实现方法,内容挺不错的,现在分享给大家,也给大家做个参考。 最近一个同事在使用Angular的时候,希望能像VUE那样,修改代码后浏览器不刷新,页面对应修改的组件自动更新的功能。这个…

    编程技术 2025年3月8日
    200
  • Angular如何正确的操作DOM

    这篇文章主要介绍了关于angular如何正确的操作dom,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下  无奈接手了一个旧项目,上一个老哥在Angular项目中大量使用了JQuery来操作DOM,真的是太不讲究了。那么如何优…

    2025年3月8日 编程技术
    200
  • 对Angular 6中滚动列表组件的封装的分析

    这篇文章给大家分享的内容是关于对angular 6中滚动列表组件的封装的分析,有一定的参考价值,有需要的朋友可以参考一下。 前言 学习应为input和output相结合之过程,这就是写这篇文章的原因。在大屏幕展示web APP中,经常会用到…

    编程技术 2025年3月8日
    200

发表回复

登录后才能评论