简单来讲变化检测就是
Angular
用来检测视图与模型之间绑定的值是否发生了改变,当检测到模型中绑定的值发生改变时,则同步到视图上,反之,当检测到视图上绑定的值发生改变时,则回调对应的绑定函数。
总结起来, 主要有以下几种状况:html
setTimeout
,setInterval
Angular并非捕捉对象的变更,它采用的是在适当的时机去检验对象的值是否被改动,这个时机就是这些异步事件的发生。
这个时机是由 Zone.js
去掌控的,它获取到了整个应用的执行上下文,可以对相关的异步事件发生、完成或者异常等进行捕获,而后驱动 Angular 的变化监测机制执行。typescript
实际上Zone,js
有一个叫猴子补丁的东西。在Zone.js
运行时,就会为这些异步事件作一层代理包裹,也就是说Zone.js运行后,调用setTimeout、addEventListener
等浏览器异步事件时,再也不是调用原生的方法,而是被猴子补丁包装事后的代理方法。代理里setup了钩子函数, 经过这些钩子函数, 能够方便的进入异步任务执行的上下文.segmentfault
//如下是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;
关于Zone.js
的详细内容能够看这篇文章。promise
Angular都是组件化的, 而且全部的组件会构成一个组件树, Angular
的变化检测就是以组件进行的,,对于每个组件,都有一个对应的changeDetector
, 所以, changeDetector
之间也是一个树状结构, 最后咱们须要记住的一点是,每次变化监测都是从 Component 树根开始的。浏览器
另外,Angular的数据流是自顶而下,从父组件到子组件单向流动。单向数据流向保证了高效、可预测的变化检测。尽管检查了父组件以后,子组件可能会改变父组件的数据使得父组件须要再次被检查,这是不被推荐的数据处理方式。在开发模式下,Angular会进行二次检查,若是出现上述状况,二次检查就会报错:Expression Changed After It Has Been Checked Error
。而在生产环境中,脏检查只会执行一次。
关于Expression Changed After It Has Been Checked Error
, 笔者曾经遇到过, 能够看看这个例子app
经过未
咱们能够主动控制Angular
的变化检测异步
它有如下方法:函数
markForCheck()
:把根组件到该组件之间的这条路径标记起来,通知Angular在下次触发变化监测时必须检查这条路径上的组件。该方法可与下文的OnPush
模式搭配使用,能够看这个例子。detach()
:从变化监测树中分离变化监测器,该组件的变化监测器将再也不执行变化监测,除非再次手动执行reattach()方法。reattach()
:把分离的变化监测器从新安装上,使得该组件及其子组件都能执行变化监测。detectChanges()
:手动触发执行该组件到各个子组件的一次变化监测。下面以使用detectChanges()
组件化
// 在组件中注入 constructor(private changeDetectorRef: ChangeDetectorRef) { } // 直接使用 test() { this.changeDetectorRef.detectChanges() }
angular 提供了两种变动检测策略,除了上述得Default外还有一种OnPush
的检测机制this
OnPush 与 Default 之间的差异: 当检测到与子组件输入绑定的值没有发生改变时,变化检测就不会深刻到子组件中去。
app.comonent.ts
@Component({ selector: 'app-root', template: ` <h1>{{title}}</h1> <h2>user.name: {{user.name}}</h2> <button type="button" (click)="changeUserName()"> 改变属性 </button> <button type="button" (click)="changeUserObject()"> 改变对象 </button> <app-test [user]="user"></app-test> `, }) export class AppComponent { title = 'OnPush Demo'; user: User = new User({name: 'yunzhi'}); changeUserName() { this.user.name = 'new name'; } changeUserObject() { this.user = new User({name: 'new user'}); } }
test.component.ts
@Component({ selector: 'app-test', template: ` <div> <h3>test 组件</h3> <p> <label>User:</label> <span>{{user.name}}</span> </p> </div>`, // 使用OnPush模式只须要加上下面这段代码 changeDetection: ChangeDetectionStrategy.OnPush }) export class TestComponent implements OnInit { @Input() user: User; constructor() { } ngOnInit() { } }
这时当咱们点击改变属性按钮时test组件显示的并不会变化,只有改变user得引用test组件显示的才会变化,以下图所示
Angular
经过优秀的变化检测机制,让咱们可以在数据发生了改变时准确的最小范围的修改DOM,同时,Angular
还提供了两种检测策略,和在一个组件中控制变化检测的方法,灵活运用这些方法可让咱们的应用更加高效,虽然如今的项目尚未到须要这种效率。