1.Angular 1 与 Angular 2 中的变化检测策略的区别
- Angular2
Angular的核心是组件化,组件的嵌套会使得最终形成一棵组件树,而每个组件都有自己的变化检测器,这意味着应用程序也是一颗变化检测器树。
另外,Angular的数据流是自顶而下的,从父组件到子组件单向流动。单向数据流向保证了高效、可预测的变化检测,尽管检查了父组件之后,子组件可能会改变父组件的数据使得父组件需要再次被检查,这是不被推荐的数据处理方式。在开发模式下,Angular会进行二次检查,如果出现上述情况,二次检查就会报错:ExpressionChangedAfterItHasBeenCheckedError。而在生产环境中,脏检查只会执行一次。
Angular1
Angular 1所采用的变化检测原理是 – 脏检查:即存储所有变量的值,每当可能有变量发生变化需要检查时,就将所有变量的旧值跟新值进行比较,不相等就说明检测到变化,需要更新对应的视图。
由于双向数据绑定的本质,在Angular 1中不能保证父节点在子节点之前总是被检查。 有可能子节点可以改变父节点或兄弟节点或树中的任何其他节点,这又会在链中触发新的更新。 这使得变化检测机制难以遍历所有节点,并可能掉入具有臭名昭着的“震荡”循环中:10 $digest() iterations reached. Aborting!
【注:这是Angular 1中的当两个方法互相watch时,就会导致不停地进行digest循环,当循环大于十次Angular就会抛出如上错误。】
二:数据何时变化
主要是以下几种情况可能改变数据:
- 用户输入操作,比如点击,提交等。
- 请求服务端数据。
- 定时事件,比如setTimeout,setInterval。
- Promise(es6新加)
这几点有个共同点,就是他们都是异步的。也就是说,所有的异步操作是可能导致数据变化的根源因素。
三:变化检测策略
1.Default
默认情况下,Angular为我们的应用程序中的每个组件定义了一个默认的变化检测策略。我们可以使用@Component装饰器的changeDetection属性使这个定义显式的体现。
@Component({
// ...
changeDetection: ChangeDetectionStrategy.Default// 表示变化检测对象的状态为`CheckAlways`
}
export class SomeComponent {
// ...
}
Angular的默认变化检测策略,也就是上述提到的脏检查,只要有值发生变化,就全部从父组件到所有子组件进行检查。
注意:对于一个对象,如果只改变其内部的属性,而不改变实例,则Angular检测不到变化(Angular比较的其实是引用),但是变化检测的默认策略还是会遍历树的所有组件。
2.OnPush
OnPush策略,就是只有当输入数据(即@Input)的引用发生变化或者有事件触发时,组件才进行变化检测。就是在你变化的时候才查你,如果不查你,你的子组件也不会查。这样就避免了无效的检测,很好的提高了性能。【我知道我没变,别查我】
@Component({
// ...
changeDetection: ChangeDetectionStrategy.OnPush// 表示变化检测对象的状态为`CheckOnce`
}
export class SomeComponent {
// ...
}
OnPush策略,对于注入属性的依赖性比较高,那么相应的对注入属性的类型就有要求了,上面我们已经说到Angular比较的是引用,那么注入对象的时候,属性变化了我们都要去new一个新实例吗?这也太麻烦了吧。那什么时候只要对象的属性值发生变化,整个对象的引用就变了呢?那就是不可变对象(Immutable Object),下面我们看看注入属性的要求。
注入属性的类型要求:
- 基本数据类型
- 不可变对象(引入Immutable.js 库)
npm install --save immutable
可观测对象.Obervable
当然,在OnPush策略下,我们也可以手动控制变化监测。【我变了,只查我】我们可以通过引用变化检测对象
ChangeDetectorRef
,可以手动去操作变化检测。我们可以在组件中通过依赖注入的方式来获取该对象:
constructor(private changeRef:ChangeDetectorRef){}
变化检测对象提供的方法有以下几种:
- markForCheck() - 在组件的 metadata 中如果设置了 changeDetection:ChangeDetectionStrategy.OnPush 条件,那么变化检测不会再次执行,除非手动调用该方法, 该方法的意思是在变化监测时必须检测该组件。
- detach() - 从变化检测树中分离变化检测器,该组件的变化检测器将不再执行变化检测,除非手动调用 reattach() 方法。(采用onPush策略之后的组件detach()无效)
- reattach() - 重新添加已分离的变化检测器,使得该组件及其子组件都能执行变化检测。
- detectChanges() - 从该组件到各个子组件执行一次变化检测。
- checkNoChanges(): 检测该组件及其子组件,如果有变化存在则报错,用于开发阶段二次验证变化已经完成。
那么,如果输入属性是Observable的话,它会订阅所有的变量变化,只要在订阅回调函数中手动触发变化检测即可实现最小成本的检测。例子如下:
@Component({
templateUrl: 'test.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
class TestCmp {
@Input() obj:Observable<any>;
public viewData:number = 0;
constructor(private changeRef: ChangeDetectorRef) {}
ngOnInit() {
this.obj.subscribe(() => {//回调
this.viewData++; // 数据模型发生变化
this.changeRef.markForCheck(); // 手动触发检测
})
}
}
四:总结
Angular与Angularjs都采用变化检测机制,前者优于后者主要体现在:
单项数据流动
以组件为单位维度独立进行检测
生产环境只进行一次检查
可自定义的变化检测策略:Default和onPush
可自定义的变化检测操作:markForcheck()、detectChanges()、detach()、reattach()、checkNoChanges()