“脏检查”是Angular中的核心机制之一,它是实现双向数据绑定、MVVM模式的重要基础。
AngularJS常用函数:$apply,$watch及$digest
$digest是一个内部函数,正常的应用代码中是不应该直接调用它的。要想主动触发它,就要调用scope.$apply函数,它是触发Angular“脏检查机制”的常用公开接口。$digest循环实际上包括两个while循环。分别是:处理$evalAsync的异步运算队列,处理$watch的watchers队列。当该循环触发时,它会遍历当前$scope及其所有子$scope上已注册的所有watchers函数。遍历一遍所有watcher函数成为一轮脏检查。执行完一轮脏检查,如果任何一个watcher所监听的值改变过,那么就会重新再进行一轮脏检查,直到所有的watcher函数都报告其所监听的值不改变了。当循环结束时,才把模型的变化结果更新到DOM中去。这样做为了,防止频繁更新DOM属性。
通俗理解脏检查:表达式{{aaa.x}},AngularJS不仅会渲染该数据,还会为该特定值创建一个观察程序。之后,只要程序发生任何事情,AngularJS就会检查该观察过程中的值是否更改。如果有,重新呈现表达式。运行这些观察者的过程称为脏检查。
Angular处理脏检查引用的是Zone.js
NgZone是一个带有基于Observables的附加api的分叉区域
想要数据发生变化应用到页面上,首先需要检测数据的变化,数据变化一般发生异步事件中,如:
浏览器事件,eg:click,submit
setTimeout和setInterval
XHR,从远程服务器获取数据……
谁通知了Angular?
class ApplicationRef {
changeDetectorRefs:ChangeDetectorRef[] = [];
// applicationRef在构造器中监听onTurnDone事件
constructor(private zone: NgZone) {
this.zone.onTurnDone
.subscribe(() => this.zone.run(() => this.tick()))
}
// tick函数遍历所有的探测器的接口/对象 对其执行检测
tick() {
this.changeDetectorRefs
.forEach((ref) => ref.detectChanges());
}
}
}
变更检测是如何进行的呢?
每一个组件都有属于自己的变更检测器(change detector)
变更检测树:有向图 单向数据流 始终都是由上而下执行更改检测
默认情况下,Angular是保守的,每次都会检查每个组件
变更检测策略
enum ChangeDetectionStrategy {
OnPush: 0 // 仅在输入已更改时才对视图进行更改检测。当输入属性不变时,Angular可以跳过整个变更检测树
Default: 1 // 默认策略,其中更改检测是自动的,直到明确停用
}
NgDoCheck钩子和变更检测
更新子组件的属性
调用位于子组件中的NgDoCheck生命周期钩子
更新当前组件的DOM
向子组件执行变更检测
NgDoCheck作用:配合markForCheck和OnPush
export class AppComponent {
@Input() data;
public id;
constructor(private cdr: ChangeDetectorRef){}
ngOnChanges() {
// 当data改变时,更新id
this.id = this.data.id;
}
ngDoCheck() {
// 在ngDoCheck中检测data这个object的属性是否更改
if( this.id !== this.data.id ) {
this.cdr.markForCheck();
}
}
}
@Component({
template:'{{num}}'
changeDetection: ChangeDetectionStrategy.OnPush
})
export class NumComponent implements OnInit {
@Input() addItem: Observable<any>;
num = 0;
constructor(private cdr: ChangeDetectorRef){}
ngOnInit() {
this.addItem.subscribe(()=>{
this.num++;
this.cdr.markForCheck();//人为通知Angular进行检测
})
}
}
Question?
为什么在OnPush策略下,即使组件没有属性更新,ngOnCheck钩子仍然被调用?
存在一种可能是子组件使用了OnPush策略而父组件没有,子组件的@Input属性没有更改,但是ngOnCheck钩子执行的是当前组件变化的调用。