Angular开发实践(五):深入解析变化监测
什么是变化监测
在使用 Angular 进行开发中,我们常用到 Angular 中的绑定——模型到视图的输入绑定、视图到模型的输出绑定以及视图与模型的双向绑定。而这些绑定的值之所以能在视图与模型之间保持同步,正是得益于Angular中的变化检测。
简单来说,变化检测就是 Angular 用来检测视图与模型之间绑定的值是否发生了改变,当检测到模型中绑定的值发生改变时,则同步到视图上,反之,当检测到视图上绑定的值发生改变时,则回调对应的绑定函数。
变化监测的源头
变化监测的关键在于如何最小粒度地监测到绑定的值是否发生了改变,那么在什么情况下会导致这些绑定的值发生变化呢?我们可以看一下我们常用的几种场景:
Events: click/hover/...
@Component({
  selector: 'demo-component',
  template: `
    <h1>{{name}}</h1>
    <button (click)="changeName()">change name</button>
  `
})
export class DemoComponent {
    name: string = 'Tom';
    changeName() {
        this.name = 'Jerry';
    }
}
我们在模板中通过插值表达式绑定了 name 属性。当点击change name按钮时,改变了 name 属性的值,这时模板视图显示内容也发生了改变。
XHR/webSocket
@Component({
  selector: 'demo-component',
  template: `
    <h1>{{name}}</h1>
  `
})
export class DemoComponent implements OnInit {
    name: string = 'Tom';
    constructor(public http: HttpClient) {}
    ngOnInit() {
        // 假设有这个./getNewName请求,返回一个新值'Jerry'
        this.http.get('./getNewName').subscribe((data: string) => {
            this.name = data;
        });
    }
}
我们在这个组件的 ngOnInit 函数里向服务器端发送了一个 Ajax 请求,当这个请求返回结果时,同样会改变当前模板视图上绑定的 name 属性的值。
Times: setTimeout/requestAnimationFrame
@Component({
  selector: 'demo-component',
  template: `
    <h1>{{name}}</h1>
  `
})
export class DemoComponent implements OnInit {
    name: string = 'Tom';
    constructor() {}
    ngOnInit() {
        // 假设有这个./getNewName请求,返回一个新值'Jerry'
        setTimeout(() => {
            this.name = 'Jerry';
        }, 1000);
    }
}
我们在这个组件的ngOnInit函数里通过设定一个定时任务,当定时任务执行时,同样会改变当前视图上绑定的name属性的值。
总结
其实,我们不难发现上述三种情况都有一个共同点,即这些导致绑定值发生改变的事件都是异步发生的。
Angular并不是捕捉对象的变动,它采用的是在适当的时机去检验对象的值是否被改动,这个时机就是这些异步事件的发生。
这个时机是由 NgZone 这个服务去掌控的,它获取到了整个应用的执行上下文,能够对相关的异步事件发生、完成或者异常等进行捕获,然后驱动 Angular 的变化监测机制执行。
变化监测的处理机制
通过上面的介绍,我们大致明白了变化检测是如何被触发的,那么 Angular 中的变化监测是如何执行的呢?
首先我们需要知道的是,对于每一个组件,都有一个对应的变化监测器;即每一个 Component 都对应有一个changeDetector,我们可以在 Component 中通过依赖注入来获取到changeDetector。
而我们的多个 Component 是一个树状结构的组织,由于一个 Component 对应一个changeDetector,那么changeDetector之间同样是一个树状结构的组织。
最后我们需要记住的一点是,每次变化监测都是从 Component 树根开始的。
举个例子
子组件:
@Component({
  selector: 'demo-child',
  template: `
    <h1>{{title}}</h1>
    <p>{{paramOne}}</p>
    <p>{{paramTwo}}</p>
  `
})
export class DemoChildComponent {
    title: string = '子组件标题';
    @Input() paramOne: any; // 输入属性1
    @Input() paramTwo: any; // 输入属性2
}
父组件:
@Component({
  selector: 'demo-parent',
  template: `
    <h1>{{title}}</h1>
    <demo-child [paramOne]='paramOneVal' [paramTwo]='paramTwoVal'></demo-child>
    <button (click)="changeVal()">change name</button>
  `
})
export class DemoParentComponent {
    title: string = '父组件标题';
    paramOneVal: any = '传递给paramOne的数据';
    paramTwoVal: any = '传递给paramTwo的数据';
    changeVal() {
        this.paramOneVal = '改变之后的传递给paramOne的数据';
    }
}
上面的代码中,DemoParentComponent 通过 标签嵌入了 DemoChildComponent,从树状结构上来说,DemoParentComponent 是 DemoChildComponent 的根节点,而 DemoChildComponent 是 DemoParentComponent 的叶子节点。
当我们点击 DemoParentComponent 的 button 时,会回调到 changeVal 方法,然后会触发变化监测的执行,变化监测流程如下:
首先变化检测从 DemoParentComponent 开始:
检测 title 值是否发生了变化:没有发生变化
检测 paramOneVal 值是否发生了变化:发生了变化(点击按钮调用changeVal()方法改变的)
检测 paramTwoVal 值是否发生了变化:没有发生变化
然后变化检测进入到叶子节点 DemoChildComponent:
检测 title 值是否发生了改变:没有发生变化
检测 paramOne 是否发生了变化:发生了改变(由于父组件的属性paramOneVal发生了改变)
检测 paramTwo 是否发生了改变:没有发生变化
最后,因为 DemoChildComponent 再也没有了叶子节点,所以变化监测将更新DOM,同步视图与模型之间的变化。
变化监测策略
学习了变化监测的处理机制之后,你可能会想,这机制未免也有点太简单粗暴了吧,假如我的应用中有成百上千个 Component,随便一个 Component 触发了监测,那么都需要从根节点到叶子节点重新检测一遍。
别着急,Angular 的开发团队已经考虑到了这个问题,上述的检测机制只是一种默认的检测机制,Angular 还提供一种 OnPush 的检测机制(设置元数据属性 changeDetection: ChangeDetectionStrategy.OnPush)。
OnPush 与 Default 之间的差别:当检测到与子组件输入绑定的值没有发生改变时,变化检测就不会深入到子组件中去。
变化监测类 - ChangeDetectorRef
上面说到我们可以修改组件元数据属性 changeDetection 来修改组件的变化监测策略(ChangeDetectionStrategy.Default 或 ChangeDetectionStrategy.OnPush),除了这个,我们还可以使用 ChangeDetectorRef 来更加灵活的控制组件的变化监测。
Angular 在整个运行期间都会为每一个组件创建 ChangeDetectorRef 的实例,该实例提供了相关方法来手动管理变化监测。有了这个类,我们自己就可以自定义组件的变化监测策略了,如停止/启用变化监测或者按指定路径变化监测等等。
相关方法如下:
markForCheck():把根组件到该组件之间的这条路径标记起来,通知Angular在下次触发变化监测时必须检查这条路径上的组件。
detach():从变化监测树中分离变化监测器,该组件的变化监测器将不再执行变化监测,除非再次手动执行reattach()方法。
reattach():把分离的变化监测器重新安装上,使得该组件及其子组件都能执行变化监测。
detectChanges():手动触发执行该组件到各个子组件的一次变化监测。
使用方法也很简单,直接在组件中注入即可:
@Component({
  selector: 'demo-parent',
  template: `
    <h1>{{title}}</h1>
  `
})
export class DemoParentComponent implements OnInit {
    title: string = '组件标题';
    constructor(public cdRef: ChangeDetectorRef) {}
    ngOnInit() {
        this.cdRef.detach(); // 停止组件的变化监测,看需求使用不同的方法
    }
}
												
											Angular开发实践(五):深入解析变化监测的更多相关文章
- Angular开发实践(一):环境准备及框架搭建
		
引言 在工作中引入Angular框架将近一年了,在这一年中不断的踩坑和填坑,当然也学习和积累了很多的知识,包括MVVM框架.前后端分离.前端工程化.SPA优化等等.因此想通过Angular开发实践这系 ...
 - Angular开发实践(七): 跨平台操作DOM及渲染器Renderer2
		
在<Angular开发实践(六):服务端渲染>这篇文章的最后,我们也提到了在服务端渲染中需要牢记的几件事件,其中就包括不要使用window. document. navigator等浏览器 ...
 - nodejs 实践:express 最佳实践(五) connect解析
		
nodejs 实践:express 最佳实践(五) connect解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固,需 ...
 - Angular开发实践(三):剖析Angular Component
		
Web Component 在介绍Angular Component之前,我们先简单了解下W3C Web Components 定义 W3C为统一组件化标准方式,提出Web Component的标准. ...
 - Angular开发实践(六):服务端渲染
		
Angular Universal Angular在服务端渲染方面提供一套前后端同构解决方案,它就是 Angular Universal(统一平台),一项在服务端运行 Angular 应用的技术. 标 ...
 - Angular开发实践(四):组件之间的交互
		
在Angular应用开发中,组件可以说是随处可见的.本篇文章将介绍几种常见的组件通讯场景,也就是让两个或多个组件之间交互的方法. 根据数据的传递方向,分为父组件向子组件传递.子组件向父组件传递及通过服 ...
 - Angular开发实践(八): 使用ng-content进行组件内容投射
		
在Angular中,组件属于特殊的指令,它的特殊之处在于它有自己的模板(html)和样式(css).因此使用组件可以使我们的代码具有强解耦.可复用.易扩展等特性.通常的组件定义如下: demo.com ...
 - httprunner开发实践&源码解析
		
上次作业讲解 排错 控制台查看报错信息 打开代理工具,调试脚本 注释掉其他接口,先跑一个接口 pip uninstall httprunner 修复断言100为int型问题 修复两次登陆问题 报告 p ...
 - ionic,Angular 开发实践
		
1.实践参考 http://www.jianshu.com/p/ea0dcf1d31c9 原文思路搭建 2. 环境搭建步骤 : a. 安装node b.安装 cordova sudo n ...
 
随机推荐
- C++的AES加解密
			
最近公司项目要做个WPF程序,但是底层加密部分要用C++来实现.通过网上搜索各种资料,地址已经记不下了,没发贴出来了! 下面看看如何加解密的~!先贴代码.... string tKey(sKey); ...
 - TOE(TCP/IP Offload Engine)网卡与一般网卡的区别
			
TCP减压引擎,第一次听说这个名词,但是并不是一个新的概念了,若干年前听说过设备厂商在研究在FPGA之中实现TCP Stack,但是后来没有听到任何的产品出来,应该是路由设备to host的traff ...
 - 深度学习word2vec笔记之基础篇
			
作者为falao_beiliu. 作者:杨超链接:http://www.zhihu.com/question/21661274/answer/19331979来源:知乎著作权归作者所有.商业转载请联系 ...
 - fastboot烧写hi3531
			
Boot Downloading started. Boot 100 % Downloaded. Boot Downloading completed! U-Boot 2010.06 (Jan 04 ...
 - 使用ffserver实现转发实时流媒体(摄像头捕获)
			
本系统为ubuntu 10.04LTS 说明1:本实验在本机成功测试通过: 说明2:本实验仅仅测试了视频流,未测试音频流. 1.配置ffserver.conf -------------------- ...
 - Netty的并发编程实践5:不要依赖线程优先级
			
当有多个线程同时运行的时候,由线程调度器来决定哪些线程运行.哪些等待以及线程切换的时间点,由于各个操作系统的线程调度器实现大相径庭,因此,依赖JDK自带的线程优先级来设置线程优先级策略的方法是错误和非 ...
 - NetBeans运行项目报错
			
1.错误描述 严重: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start co ...
 - 芝麻HTTP:爬虫的基本原理
			
我们可以把互联网比作一张大网,而爬虫(即网络爬虫)便是在网上爬行的蜘蛛.把网的节点比作一个个网页,爬虫爬到这就相当于访问了该页面,获取了其信息.可以把节点间的连线比作网页与网页之间的链接关系,这样蜘蛛 ...
 - pat1111-1120
			
1111 比较麻烦的最短路 #include<cmath> #include<map> #include<iostream> #include<cstring ...
 - Duplicate entry '0' for key 'PRIMARY'的一种可能的解决办法
			
在MySQL设计好数据库往往数据库中插入数据的时候, 因为主键ID默认是不赋值的,只给其他项目赋值了,相关的SQL代码是这样的 StringBuilder strSql = new StringBui ...