本文链接:https://blog.csdn.net/fen747042796/article/details/75152336

前端展示的页面是由视图和数据共同构成的,视图模板定义了页面的框架,而数据定义了页面具体的显示内容。

而数据发生变化的时候,我们需要及时将变化的内容更新到视图中,否则用户看到的数据就是不正确的。系统及时感知到数据模型的变化,然后通过计算更新到视图中,这是每个前端框架都需要解决的问题。这前一半部分就是所谓的变化检测。

数据何时变化

接下来的问题是,数据何时变化,哪些因素会引起数据变化?在数据双向绑定的分析和简单实现中曾经分析过,主要有如下几种情况可能也改变数据:

  • 用户输入操作,比如点击,提交等
  • 请求服务端数据
  • 定时事件,比如setTimeout,setInterval

这几点有一个共同点,就是它们都是异步的。也就是说,所有的异步操作是可能导致数据变化的根源因素。

如何通知变化

那么,在Angular中是谁来通知数据即将变化的呢?在AngularJS中是由代码$scope.$apply()或者$scope.$digest触发,而Angular接入了ZoneJS,由它监听了Angular所有的异步事件。ZoneJS是怎么做到的呢?其实它重写了所有的异步api(所谓的猴子补丁Monkey patch)!ZoneJS会通知Angular可能有数据发生变化,需要检测更新。

变化检测原理

Angular得到需要重新检查数据模型,更新视图的通知后,是怎么执行变化检测的呢?答案是,脏检查。考虑到还没听说过脏检查的同学,这里解释一下,脏检查其实就是存储所有变量的值,每当可能有变量发生变化需要检查时,就将所有变量的旧值跟新值进行比较,不相等就说明检测到变化,需要更新对应视图。

改善的脏检查

接触过AngularJS的同学肯定知道,它使用的变化检测机制也是脏检查。那么,同是脏检查的背后,有何不同呢?为何Angular自称变化检测的性能比起AngularJS提升了很多?

Angular的核心是组件化,组件的嵌套会使得最终形成一棵组件树。Angular的变化检测可以分组件进行,每个组件都有对应的变化检测器ChangeDetector。可想而知,这些变化检测器也会构成一棵树。

另外,Angular的数据流是自顶而下,从父组件到子组件单向流动。单向数据流向保证了高效、可预测的变化检测。尽管检查了父组件之后,子组件可能会改变父组件的数据使得父组件需要再次被检查,这是不被推荐的数据处理方式。在开发模式下,Angular会进行二次检查,如果出现上述情况,二次检查就会报错:ExpressionChangedAfterItHasBeenCheckedError(关于这个问题的答案,可以在参考资料中找到)。而在生产环境中,脏检查只会执行一次。

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

变化检测策略onPush

Angular还让开发者拥有定制变化检测策略的能力。

  1. export enum ChangeDetectionStrategy {
  2. OnPush, // 表示变化检测对象的状态为`CheckOnce`
  3. Default, // 表示变化检测对象的状态为`CheckAlways`
  4. }

ChangeDetectionStrategy可以看到,Angular有两种变化检测策略。Default是Angular默认的变化检测策略,也就是上述提到的脏检查(只要有值发生变化,就全部检查)。开发者可以根据场景来设置更加高效的变化检测方式:onPushonPush策略,就是只有当输入数据的引用发生变化或者有事件触发时,组件才进行变化检测。

  1. @Component({
  2. template: `
  3. <h2>{{vData.name}}</h2>
  4. <span>{{vData.email}}</span>
  5. `,
  6. // 设置该组件的变化检测策略为onPush
  7. changeDetection: ChangeDetectionStrategy.OnPush
  8. })
  9. class VCardCmp {
  10. @Input() vData;
  11. }

比如上面这个例子,当vData的属性值发生变化的时候,这个组件不会发生变化检测,只有当vData重新赋值的时候才会。一般,只接受输入的木偶子组件(dumb components)比较适合采用onPush策略。

那什么时候只要对象的属性值发生变化,整个对象的引用就变了呢?不可变对象(Immutable Object)。当组件中的输入对象是不变量时,可采用onPush变化检测策略,减少变化检测的频率。换个角度来说,为了更加智能地执行变化检测,可以在只接受输入的子组件中采用onPush策略。

变化检测对象引用

Angular不仅可以让开发者设置变化检测的策略,还可以让开发者获取变化检测对象引用ChangeDetectorRef,手动去操作变化检测。变化检测对象引用给开发者提供的方法有以下几种:

  • markForCheck():将检查组件的所有父组件所有子组件,即使设置了变化检测策略为onPush
  • detach():将变化检测对象脱离检测对象树,不再进行变化检查;结合detectChanges可实现局部变化检测
  • detectChanges():将检测该组件及其子组件,结合detach可实现局部检测
  • checkNoChanges(): 检测该组件及其子组件,如果有变化存在则报错,用于开发阶段二次验证变化已经完成
  • reattach():将脱离的变化检测对象重新链接到变化检测树上

那么,如果是Observable的话,它会订阅所有的变量变化,只要在订阅回调函数中手动触发变化检测即可实现最小成本的检测(仍采用onPush变化检测策略)。举个例子:

  1. @Component({
  2. template: '{{counter}}',
  3. changeDetection: ChangeDetectionStrategy.OnPush
  4. })
  5. class CartBadgeCmp {
  6. @Input() addItemStream:Observable<any>;
  7. counter = 0;
  8. constructor(private cd: ChangeDetectorRef) {}
  9. ngOnInit() {
  10. this.addItemStream.subscribe(() => {
  11. this.counter++; // 数据模型发生变化
  12. this.cd.markForCheck(); // 手动触发检测
  13. })
  14. }
  15. }

另外,当数据模型变化太过频繁,我们可自定义变化检测的时机。举个例子:

  1. @Component({
  2. template: `{{counter}}
  3. <input type="check" (click)="toggle()">`,
  4. })
  5. class CartBadgeCmp {
  6. counter = 0;
  7. detectEnabled = false;
  8. constructor(private cd: ChangeDetectorRef) {}
  9. ngOnInit() {
  10. // 每10毫秒增加1
  11. setInterval(()=>{this.counter++}, 10);
  12. }
  13. toggle(){
  14. if( this.detectEnabled ){
  15. this.cd.reattach(); // 链接上变化检测树
  16. }
  17. else{
  18. this.cd.detach(); // 脱离变化检测树
  19. }
  20. }
  21. }

一个注意点是,采用onPush策略之后的组件detach()无效,具体可参考这里。

疑惑点

在Angular源码中看到变化检测对象有如下几种状态: 
CheckOnce:表示只检查一次,调用detectChanges之后状态将会变为Checked 
Checked:表示在状态变为CheckOnce之前会跳过所有检测 
CheckAlways:表示总是接受变化检测,每次调用detectChanges后状态还是CheckAlways 
Detached:代表变化检测对象脱离了变化检测对象树,不再进行变化检测 
Errored:表述变化测试对象发生错误,变化检测实效 
Destroyed:表示变化检测对象已经被销毁

OnPush策略表示变化检测对象的状态为CheckOnce。那么设置OnPush策略的组件为什么是引用发生变化之后才会执行变化检测的?检测之后状态从CheckOnce变成Checked,然后是如何变成CheckOnce的?

P.S. 尝试阅读Angular变化检测这部分的源代码,实在不知从何下手,网上资料甚少,求阅读源码经验分享。

总结

Angular与AngularJS都采用脏检查的变化检测机制,前者优于后者主要体现在:

  • 单向数据流向
  • 以组件为单位维度独立进行
  • 生产环境只进行一次检查
  • 可自定义的变化检测策略: DefaultonPush
  • 可自定义的变化检测操作:markForcheck()detectChanges()detach(), reattach()checkNoChanges()
  • 代码实现上的优化,据说采用了VM friendly的代码(这点我也不太明白,就随便提一下)

参考资料:

  • Tero Parviainen: Change And Its Detection In JavaScript Frameworks
  • Victor Savkin: Change Detection in Angular
  • Pascal Precht: Angular Change Dection Explained
  • Maxim Koretskyi:Everything you need to know about change detection in Angular
  • Maxim Koretskyi:Angular’s $digest is reborn in the newer version of Angular
  • Wojciech Kwiatek:Understanding Angular 2 change detection
  • Ben Nadel: Change Detection Strategy Appears To Override The ChangeDetectorRef In Angular 2 RC 3
  • Juri: Tuning Angular’s Change Detection
  • Maxim Koretskyi:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error

Angular变化检测机制:改善的脏检查相关推荐

  1. Angular变化检测机制

    2019独角兽企业重金招聘Python工程师标准>>> 在使用Angular进行开发中,我们常用到Angular中的绑定--模型到视图的输入绑定.视图到模型的输出绑定以及视图与模型的 ...

  2. Angular 变化检测详解

    作者:PingCode 产品研发部研发二组负责人王凯 前言 变化检测是前端框架中很有趣的一部分内容,各个前端的框架也都有自己的一套方案,一般情况下我们不太需要过多的了解变化检测,因为框架已经帮我们完成 ...

  3. hibernate脏数据_Hibernate脏检查的剖析

    hibernate脏数据 介绍 持久性上下文使实体状态转换入队 ,该实体状态转换在刷新后转换为数据库语句. 对于托管实体,Hibernate可以代表我们自动检测传入的更改并安排SQL UPDATE. ...

  4. Hibernate脏检查的剖析

    介绍 持久性上下文使实体状态转换进入队列,该实体状态转换在刷新后转换为数据库语句. 对于托管实体,Hibernate可以代表我们自动检测传入的更改并安排SQL UPDATE. 这种机制称为自动脏检查 ...

  5. 手写AngularJS脏检查机制

    什么是脏检查 View -> Model 浏览器提供有User Event触发事件的API,例如,click,change等 Model -> View 浏览器没有数据监测API. Ang ...

  6. hibernate自定义_如何自定义Hibernate脏检查机制

    hibernate自定义 介绍 在上一篇文章中,我描述了Hibernate自动脏检查机制. 尽管您应该始终喜欢它,但是有时您可能想添加自己的自定义污垢检测策略. 自定义脏检查策略 Hibernate提 ...

  7. 如何自定义Hibernate脏检查机制

    介绍 在上一篇文章中,我描述了Hibernate自动脏检查机制. 尽管您应该始终喜欢它,但是有时您可能想添加自己的自定义污垢检测策略. 自定义脏检查策略 Hibernate提供以下定制机制: 休眠拦截 ...

  8. 深入理解 Angular 变化检测(change detection)

    引言 本文分享一些讲解Angular Change Detection的文章,并指出其中有意思的内容,以及自己的一些总结和引申. Angular Change Detection Explained ...

  9. 前端模式 VD, 脏检查 MVVM ,数据收集 MVVM 所使用的场合

    VD 虚拟DOM 数据变化,先修改虚拟DOM层,然后通过虚拟DOM树的对比检查获取出最小的修改量进行对真实DOM树进行修改.虚拟DOM模式只是在DOM层的检查,所以初始渲染速度非常快.在细小修改的大量 ...

最新文章

  1. Windows系统中文件解说
  2. Coding4Fun Toolkit支持本地化解决办法
  3. 负债的阶梯,你在第几层?
  4. activity直接销毁_Android -- Activity的销毁和重建
  5. 20155305乔磊2016-2017-2《Java程序设计》第四周学习总结
  6. 计算机在岗位上的应用,计算机岗位应用论文.doc
  7. 不需要训练数据的图像恢复
  8. 【转】NHibernate集合映射中的set, list, map, bag, array
  9. 《简明 PHP 教程》03 第一步
  10. python django 优势_为什么选择Django?
  11. pfn_to_page 函数
  12. Linux内核--网络栈实现分析(一)--网络栈初始化
  13. ITK实现DICM图像转换成BMP图像
  14. 思科 ASA5505 防火墙放行流量简单配置案例
  15. 联想android手机驱动,Lenovo联想手机驱动
  16. Windows配置域名
  17. mysql多进程模块型_mysql mysqld_multi 单机多进程
  18. WINRAR5.0破解
  19. hadoop之MapReduce统计选修课程人数,不及格门数,选课人数
  20. C++获取字符串长度

热门文章

  1. centos6.6 安装python环境及Django 1.9.0
  2. 思维模型篇:数据分析的本质是什么?
  3. 注解 @EnableFeignClients 工作原理
  4. Delta Lake在Soul的应用实践
  5. 分类模型与排序模型在推荐系统中的异同分析
  6. 使用ThreadLocal不当可能会导致内存泄露
  7. 通过java api操作hdfs(kerberos认证)
  8. java操作hdfs文件、文件夹
  9. Scala Hbase 问题汇总
  10. 聊聊高并发(二十一)解析java.util.concurrent各个组件(三) 深入理解AQS(一)