引言
使用过现代JavaScript框架的开发者,都应该熟悉绑定(binding)的概念。绑定通常有两个方向。一是由用户交互驱动,在浏览器的页面上发生了输入、点击操作,导致应用程序的状态发生改变,这些改变需要反映到程序中特定变量上。另一个方向是,JavaScript代码的业务逻辑中改变了程序的状态,比如通过API请求拿到新的数据,而这些状态也需要反映的页面的控件上。很多框架如AngularJS就实现了双向绑定机制。
下面我们来看上述第二种绑定,即程序状态从业务代码到前端页面的传递过程。如果沃恩自己去实现应该怎么做呢?最直观的想法是:在恰当的时候对程序中的变量表达式进行求值,看看它是否与原来的值相同。如果不同,则把新的值写入与页面渲染相关的对象中。这里面检查变量表达式的值是否变化的过程就是所谓的变更检测(change detection)。Angular就是采用变更检测实现绑定的。下面我们具体来看。
基本原理
程序状态变化的来源有以下几种,首先是响应用户在UI上的操作,比如用户点击了一个按钮,改变了某个变量的值。然后是浏览器的异步事件,比如setTimeout
的回调函数,改变了某个变量。最后是应用程序中的异步事件,比如API返回的Promise或者Observable对象在Resolve时,改变了变量的值。那么Angular是如何知道这些状态的改变呢?
我们知道,Angular的组件可以依赖其他的组件来构建应用程序的页面逻辑,最后形成一棵组件树。每个组件都有自己的变更检测器(change detector)。因此,变更检测器的结构也是一棵同构的树(见下图)。
当某个组件的状态发生改变时,Angular会从这棵树的根节点开始遍历,出发所有组件节点的变更检测器,这样Angular就知道那些组件的状态发生了改变,需要更新相应的UI(见下图)。这个过程看似开销很大,但Angular已经进行了大量优化,实际变更检测的速度很快。
上述变更检测的策略是Angular的默认行为。事实上,我们可以通过ChangeDetectionStrategy
对象来配置某个组件的变更检测策略。如果不指定,该对象的值是Default
。在默认情况下,某个组件的变更检测触发,受其他组件的影响。那么如何让组件只关注自己的内部的变化呢?答案是设置ChangeDetectionStrategy
的值为OnPush
。
OnPush策略
在OnPush策略下,只有两种情况可以触发当前组件的变更检测:
- 组件的输入属性(绑定)的引用被改变
- 组件内部触发了异步事件
我们先来看第一点,这里关键词是引用。比如下面这个组件:
@Component({
template: `
<test [config]="config"></test>
`
})
export class AppComponent {
config = {
name: 'jtz'
};
onClick() {
this.config.name = 'code';
}
}
我们在