概述
简单来说变化检测就是Angular 用来检测视图与模型之间绑定的值是否发生了改变,当检测到模型中绑定的值发生改变时,则同步到视图上,反之,当检测到视图上绑定的值发生改变时,则回调对应的绑定函数。
什么情况下会引起变化检测?
总结起来,主要有如下几种情况可能也改变数据:
- 用户输入操作,比如点击,提交等
- 请求服务端数据(XHR)
- 定时事件,比如
setTimeout ,setInterval
上述三种情况都有一个共同点,即这些导致绑定值发生改变的事件都是异步发生的。如果这些异步的事件在发生时能够通知到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有两种变化检测策略。Default是Angular默认的变化检测策略,也就是上述提到的脏检查(只要有值发生变化,就全部从父组件到子组件检查)。开发者可以根据场景来设置更加高效的变化检测方式:onPush。onPush策略,就是只有当输入数据(即@Input )的引用 发生变化或者有事件触发时,组件才进行变化检测。
@Component({
template: `
<h2>{{vData.name}}</h2>
<span>{{vData.email}}</span>
`,// 设置该组件的变化检测策略为onPush
changeDetection: ChangeDetectionStrategy.OnPush
})
class VCardCmp {
@Input() vData;
}
比如上面这个例子,当vData 的属性值发生变化的时候,这个组件不会发生变化检测,这是因为vData 的引用并没有发生变化,只有当vData 重新赋值的时候才会. (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|