在使用 Angular 进行开发中,咱们经常使用到 Angular 中的绑定——模型到视图的输入绑定、视图到模型的输出绑定以及视图与模型的双向绑定。而这些绑定的值之因此能在视图与模型之间保持同步,正是得益于Angular中的变化检测。javascript
简单来讲,变化检测就是 Angular 用来检测视图与模型之间绑定的值是否发生了改变,当检测到模型中绑定的值发生改变时,则同步到视图上,反之,当检测到视图上绑定的值发生改变时,则回调对应的绑定函数。java
变化监测的关键在于如何最小粒度地监测到绑定的值是否发生了改变,那么在什么状况下会致使这些绑定的值发生变化呢?咱们能够看一下咱们经常使用的几种场景:web
@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 属性的值,这时模板视图显示内容也发生了改变。服务器
@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 属性的值。异步
@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 中的变化监测是如何执行的呢?学习
首先咱们须要知道的是,对于每个组件,都有一个对应的变化监测器;即每个 Component 都对应有一个changeDetector
,咱们能够在 Component 中经过依赖注入来获取到changeDetector
。this
而咱们的多个 Component 是一个树状结构的组织,因为一个 Component 对应一个changeDetector
,那么changeDetector
之间一样是一个树状结构的组织。双向绑定
最后咱们须要记住的一点是,每次变化监测都是从 Component 树根开始的。code
子组件:
@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 经过 <demo-child></demo-child> 标签嵌入了 DemoChildComponent,从树状结构上来讲,DemoParentComponent 是 DemoChildComponent 的根节点,而 DemoChildComponent 是 DemoParentComponent 的叶子节点。
当咱们点击 DemoParentComponent 的 button 时,会回调到 changeVal 方法,而后会触发变化监测的执行,变化监测流程以下:
首先变化检测从 DemoParentComponent 开始:
而后变化检测进入到叶子节点 DemoChildComponent:
最后,由于 DemoChildComponent 再也没有了叶子节点,因此变化监测将更新DOM,同步视图与模型之间的变化。
学习了变化监测的处理机制以后,你可能会想,这机制未免也有点太简单粗暴了吧,假如个人应用中有成百上千个 Component,随便一个 Component 触发了监测,那么都须要从根节点到叶子节点从新检测一遍。
别着急,Angular 的开发团队已经考虑到了这个问题,上述的检测机制只是一种默认的检测机制,Angular 还提供一种 OnPush 的检测机制(设置元数据属性 changeDetection: ChangeDetectionStrategy.OnPush)。
OnPush 与 Default 之间的差异:当检测到与子组件输入绑定的值没有发生改变时,变化检测就不会深刻到子组件中去。
上面说到咱们能够修改组件元数据属性 changeDetection 来修改组件的变化监测策略(ChangeDetectionStrategy.Default 或 ChangeDetectionStrategy.OnPush),除了这个,咱们还可使用 ChangeDetectorRef 来更加灵活的控制组件的变化监测。
Angular 在整个运行期间都会为每个组件建立 ChangeDetectorRef 的实例,该实例提供了相关方法来手动管理变化监测。有了这个类,咱们本身就能够自定义组件的变化监测策略了,如中止/启用变化监测或者按指定路径变化监测等等。
相关方法以下:
使用方法也很简单,直接在组件中注入便可:
@Component({ selector: 'demo-parent', template: ` <h1>{{title}}</h1> ` }) export class DemoParentComponent implements OnInit { title: string = '组件标题'; constructor(public cdRef: ChangeDetectorRef) {} ngOnInit() { this.cdRef.detach(); // 中止组件的变化监测,看需求使用不一样的方法 } }