0

0

详细解读Angular系列中变化检测问题

亚连

亚连

发布时间:2018-06-04 14:39:39

|

1530人浏览过

|

来源于php中文网

原创

这篇文章主要介绍了详解angular系列之变化检测(change detection),现在分享给大家,也给大家做个参考。

概述

简单来说变化检测就是Angular用来检测视图与模型之间绑定的值是否发生了改变,当检测到模型中绑定的值发生改变时,则同步到视图上,反之,当检测到视图上绑定的值发生改变时,则回调对应的绑定函数。

什么情况下会引起变化检测?

总结起来, 主要有如下几种情况可能也改变数据:

  1. 用户输入操作,比如点击,提交等

  2. 请求服务端数据(XHR)

  3. 定时事件,比如setTimeout,setInterval

上述三种情况都有一个共同点,即这些导致绑定值发生改变的事件都是异步发生的。如果这些异步的事件在发生时能够通知到Angular框架,那么Angular框架就能及时的检测到变化。

左边表示将要运行的代码,这里的stack表示Javascript的运行栈,而webApi则是浏览器中提供的一些Javascript的API,TaskQueue表示Javascript中任务队列,因为Javascript是单线程的,异步任务在任务队列中执行。

具体来说,异步执行的运行机制如下:

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

  2. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之 中放置一个事件。

  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

  4. 主线程不断重复上面的第三步。

当上述代码在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)的引用发生变化或者有事件触发时,组件才进行变化检测。

defalut 策略

main.component.ts

@Component({
 selector: 'app-root',
 template: `
 

变更检测策略

{{ slogan }}

`, }) export class AppComponent { slogan: string = 'change detection'; title: string = 'default 策略'; star: Star = new Star('周', '杰伦'); changeStar() { this.star.firstName = '吴'; this.star.lastName = '彦祖'; } changeStarObject() { this.star = new Star('刘', '德华'); } }

movie.component.ts

@Component({
 selector: 'movie',
 styles: ['p {border: 1px solid black}'],
 template: `

{{ title }}

{{star.firstName}} {{star.lastName}}

`, }) export class MovieComponent { @Input() title: string; @Input() star; }

上面代码中, 当点击第一个按钮改变明星属性时,依次对slogan, title, star三个属性进行检测, 此时三个属性都没有变化, star没有发生变化,是因为实质上在对star检测时只检测star本身的引用值是否发生了改变,改变star的属性值并未改变star本身的引用,因此是没有发生变化。

而当我们点击第二个按钮改变明星对象时 ,重新new了一个 star ,这时变化检测才会检测到 star发生了改变。

然后变化检测进入到子组件中,检测到star.firstName和star.lastName发生了变化, 然后更新视图.

OnPush策略

Noiz AI
Noiz AI

AI语音合成、声音克隆

下载

与上面代码相比, 只在movie.component.ts中的@component中增加了一行代码:

changeDetection:ChangeDetectionStrategy.OnPush
此时, 当点击第一个按钮时, 检测到star没有发生变化, ok,变化检测到此结束, 不会进入到子组件中, 视图不会发生变化.

当点击第二个按钮时,检测到star发生了变化, 然后变化检测进入到子组件中,检测到star.firstName和star.lastName发生了变化, 然后更新视图.

所以,当你使用了OnPush检测机制时,在修改一个绑定值的属性时,要确保同时修改到了绑定值本身的引用。但是每次需要改变属性值的时候去new一个新的对象会很麻烦,immutable.js 你值得拥有!

变化检测对象引用

通过引用变化检测对象ChangeDetectorRef,可以手动去操作变化检测。我们可以在组件中的通过依赖注入的方式来获取该对象:

constructor(
  private changeRef:ChangeDetectorRef
 ){}

变化检测对象提供的方法有以下几种:

  1. markForCheck() - 在组件的 metadata 中如果设置了 changeDetection:ChangeDetectionStrategy.OnPush 条件,那么变化检测不会再次执行,除非手动调用该方法, 该方法的意思是在变化监测时必须检测该组件。

  2. detach() - 从变化检测树中分离变化检测器,该组件的变化检测器将不再执行变化检测,除非手动调用 reattach() 方法。

  3. reattach() - 重新添加已分离的变化检测器,使得该组件及其子组件都能执行变化检测

  4. detectChanges() - 从该组件到各个子组件执行一次变化检测

OnPush策略下手动发起变化检测

组件中添加事件改变输入属性

在上面代码movie.component.ts中修改如下

@Component({
 selector: 'movie',
 styles: ['p {border: 1px solid black}'],
 template: `

{{ title }}

{{star.firstName}} {{star.lastName}}

`, changeDetection:ChangeDetectionStrategy.OnPush }) export class MovieComponent { constructor( private changeRef:ChangeDetectorRef ){} @Input() title: string; @Input() star; changeStar(){ this.star.lastName = 'xjl'; } }

此时点击按钮切换名字时,star更改如下

![图片描述][3]

第二种就是上面讲到的使用变化检测对象中的 markForCheck()方法.

ngOnInit() {
  setInterval(() => {
   this.star.lastName = 'xjl';
   this.changeRef.markForCheck();
  }, 1000);
 }

输入属性为Observable

修改app.component.ts

@Component({
 selector: 'app-root',
 template: `
 

变更检测策略

{{ slogan }}

`, }) export class AppComponent implements OnInit{ slogan: string = 'change detection'; title: string = 'OnPush 策略'; star: Star = new Star('周', '杰伦'); count:Observable; ngOnInit(){ this.count = Observable.timer(0, 1000) } changeStar() { this.star.firstName = '吴'; this.star.lastName = '彦祖'; } changeStarObject() { this.star = new Star('刘', '德华'); } }

此时,有两种方式让MovieComponent进入检测,一种是使用变化检测对象中的 markForCheck()方法.

ngOnInit() {
  this.addCount.subscribe(() => {
   this.count++;
   this.changeRef.markForCheck();
  })

另外一种是使用async pipe 管道

@Component({
 selector: 'movie',
 styles: ['p {border: 1px solid black}'],
 template: `

{{ title }}

{{star.firstName}} {{star.lastName}}

{{addCount | async}}

`, changeDetection: ChangeDetectionStrategy.OnPush })

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

利用vue实现如何在表格里,取每行的id(详细教程)

利用vue移动端UI框架如何实现QQ侧边菜单(详细教程)

使用vue及element组件的安装教程(详细教程)

相关专题

更多
高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

72

2026.01.16

全民K歌得高分教程大全
全民K歌得高分教程大全

本专题整合了全民K歌得高分技巧汇总,阅读专题下面的文章了解更多详细内容。

132

2026.01.16

C++ 单元测试与代码质量保障
C++ 单元测试与代码质量保障

本专题系统讲解 C++ 在单元测试与代码质量保障方面的实战方法,包括测试驱动开发理念、Google Test/Google Mock 的使用、测试用例设计、边界条件验证、持续集成中的自动化测试流程,以及常见代码质量问题的发现与修复。通过工程化示例,帮助开发者建立 可测试、可维护、高质量的 C++ 项目体系。

54

2026.01.16

java数据库连接教程大全
java数据库连接教程大全

本专题整合了java数据库连接相关教程,阅读专题下面的文章了解更多详细内容。

39

2026.01.15

Java音频处理教程汇总
Java音频处理教程汇总

本专题整合了java音频处理教程大全,阅读专题下面的文章了解更多详细内容。

19

2026.01.15

windows查看wifi密码教程大全
windows查看wifi密码教程大全

本专题整合了windows查看wifi密码教程大全,阅读专题下面的文章了解更多详细内容。

85

2026.01.15

浏览器缓存清理方法汇总
浏览器缓存清理方法汇总

本专题整合了浏览器缓存清理教程汇总,阅读专题下面的文章了解更多详细内容。

43

2026.01.15

ps图片相关教程汇总
ps图片相关教程汇总

本专题整合了ps图片设置相关教程合集,阅读专题下面的文章了解更多详细内容。

11

2026.01.15

ppt一键生成相关合集
ppt一键生成相关合集

本专题整合了ppt一键生成相关教程汇总,阅读专题下面的的文章了解更多详细内容。

49

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Vue.js:纪录片
Vue.js:纪录片

共1课时 | 0.2万人学习

Angular js入门篇
Angular js入门篇

共17课时 | 3.5万人学习

Excel 教程
Excel 教程

共162课时 | 12.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号