变化检测的基本功能就是获取应用程序的内部状态(state),而且是将这种状态对用户界面保持可见.状态能够是javascript中的任何的数据结构,好比对象,数组,(数字,布尔,字符串等基础数据类型).这种状态最终可能成为用户界面中的段落,表单,连接或按钮,在web浏览器中咱们们称之为文档对象模型(dom).将数据结构做为输入生成dom做为输出并展现给用户,咱们称这个过程为渲染.javascript
可是,在运行时发生更改时会变得更加棘手。一段时间后,DOM已经被渲染。咱们如何弄清楚模型中发生了哪些变化,以及咱们须要在哪里更新DOM?访问DOM树老是很昂贵,所以咱们不只须要找出须要更新的位置,并且还但愿尽量小地保持访问权限.这能够经过许多不一样的方式解决。例如,一种方法是简单地发出http请求并从新呈现整个页面。另外一种方法是将新状态的DOM与先前状态进行区分并仅渲染差别的概念,这就是React使用Virtual DOM进行的操做.java
所以,变动检测的目标始终是预测数据及其变化。web
如今咱们知道变化检测的所有内容,咱们可能想知道,什么时候会发生这样的变化? Angular何时知道它必须更新视图?好吧,咱们来看看下面的代码json
@Component({ template: ` <h1>{{firstname}} {{lastname}}</h1> <button (click)="changeName()">Change name</button> ` }) class MyApp { firstname:string = 'Pascal'; lastname:string = 'Precht'; changeName() { this.firstname = 'Brad'; this.lastname = 'Green'; } }
上面的组件只显示两个属性,并提供了一种方法,能够在单击模板中的按钮时更改它们。单击此特定按钮的那一刻是应用程序状态发生更改的时刻,由于它会更改组件的属性。那是咱们想要更新视图的那一刻。数组
又好比:浏览器
@Component() class ContactsApp implements OnInit{ contacts:Contact[] = []; constructor(private http: Http) {} ngOnInit() { this.http.get('/contacts') .map(res => res.json()) .subscribe(contacts => this.contacts = contacts); } }
该组件包含联系人列表,在初始化时,它会执行http请求。一旦此请求返回,列表就会更新。一样,此时,咱们的应用程序状态已更改,所以咱们将要更新视图。服务器
应用程序状态改变通常由如下3个方面引发:数据结构
事实证实,这三件事有一些共同之处。你能说出来吗? ......正确!它们都是异步的.框架
为何你认为这很重要?嗯......由于事实证实,当Angular真正对更新视图感兴趣时,这些是惟一的状况。假设咱们有一个Angular组件,当单击一个按钮时它会执行一个处理程序:dom
@Component({ selector: 'my-component', template: ` <h3>We love {{name}}</h3> <button (click)="changeName()">Change name</button> ` }) class MyComponent { name:string = 'thoughtram'; changeName() { this.name = 'Angular'; } }
单击组件的按钮时,将执行changeName(),这将更改组件的name属性,因为咱们但愿此更改也反映在DOM中,所以Angular将相应地更新视图绑定{{name}}。很好,这彷佛神奇地工做.
另外一个例子是使用setTimeout()更新name属性。请注意,咱们删除了该按钮。咱们不是必须去作一些特殊的事情来通知框架状态 发生了变化
@Component({ selector: 'my-component', template: ` <h3>We love {{name}}</h3> ` }) class MyComponent implements OnInit { name:string = 'thoughtram'; ngOnInit() { setTimeout(() => { this.name = 'Angular'; }, 1000); } }
Angular容许咱们直接使用本机API。咱们不须要调用拦截器方法,所以Angular会通知更新DOM。这是纯粹的魔法吗? 背后的秘密就是Angular利用了Zones库.Zones猴子补丁全局异步操做,如setTimeout()和addEventListener(),这就是Angular能够轻松找到的缘由,什么时候更新DOM. 简短的版本是,在Angular的源代码中,有一个名为ApplicationRef的东西,它监听NgZones onTurnDone事件。每当触发此事件时,它都会执行tick()函数,该函数基本上执行更改检测。
// very simplified version of actual source class ApplicationRef { changeDetectorRefs:ChangeDetectorRef[] = []; constructor(private zone: NgZone) { this.zone.onTurnDone .subscribe(() => this.zone.run(() => this.tick()); } tick() { this.changeDetectorRefs .forEach((ref) => ref.detectChanges()); } }
一个重要的事实是:咱们能够为每一个组件单独控制如何以及什么时候执行更改检测
因为每一个组件都有本身的更改检测器,而Angular应用程序由组件树组成,所以逻辑结果是咱们也有一个更改检测器树。此树也能够视为有向图,其中数据始终从顶部流向底部.数据从上到下流动的缘由是由于每一个单独的组件,从根组件开始,每一个组件也始终从上到下执行更改检测。这很棒,由于单向数据流比循环更容易预测。相比之下,AngularJS采用的是双向数据流,错综复杂的数据流使得它不得很少次检查,使得数据最终趋向稳定。理论上,数据可能永远不稳定。AngularJS给出的策略是,脏检查超过10次,就认为程序有问题,再也不进行检查。咱们老是知道咱们在视图中使用的数据来自何处,由于它只能来自其组件。在Angular 2+中,另外一个有趣的观察是一次经过后变化检测变得稳定。这意味着,若是咱们的某个组件在更改检测期间第一次运行后致使任何其余反作用,Angular将抛出错误。在开发模式下,Angular会进行二次检查,若是出现上述状况,二次检查就会报错:Expression Changed After It Has Been Checked Error。而在生产环境中,脏检查只会执行一次。
默认状况下,即便咱们每次都要检查事件发生时每一个组件,Angular都很是快。它能够在几毫秒内执行数十万次检查。这主要是由于Angular生成了VM友好代码。那是什么意思?好吧,当咱们说每一个组件都有本身的变化检测器时,它不像Angular中的这个通用的东西,它负责每一个组件的变化检测。缘由是它必须以动态方式编写,所以不管模型结构如何,它均可以检查每一个组件。虚拟机不喜欢这种动态代码,由于它们没法对其进行优化。它被认为是多态的,由于物体的形状并不老是相同的。
Angular在运行时为每一个组件建立变化检测器类,这些组件是单态的,由于它们确切地知道组件模型的形状。 VM能够完美地优化此代码,从而使其执行起来很是快。好消息是咱们没必要过多关心它,由于Angular会自动完成它。
本篇简单介绍下angular 2+变化检测的基础,下一篇重点讲一下变化检测策略.