什么是变化检测?

变化检测的基本功能就是获取应用程序的内部状态(state),并且是将这种状态对用户界面保持可见.状态可以是javascript中的任何的数据结构,比如对象,数组,(数字,布尔,字符串等基础数据类型).这种状态最终可能成为用户界面中的段落,表单,链接或按钮,在web浏览器中我们们称之为文档对象模型(dom).将数据结构作为输入生成dom作为输出并展示给用户,我们称这个过程为渲染.

但是,在运行时发生更改时会变得更加棘手。一段时间后,DOM已经被渲染。我们如何弄清楚模型中发生了哪些变化,以及我们需要在哪里更新DOM?访问DOM树总是很昂贵,因此我们不仅需要找出需要更新的位置,而且还希望尽可能小地保持访问权限.这可以通过许多不同的方式解决。例如,一种方法是简单地发出http请求并重新呈现整个页面。另一种方法是将新状态的DOM与先前状态进行区分并仅渲染差异的概念,这就是React使用Virtual DOM进行的操作.

因此,变更检测的目标始终是预测数据及其变化。

什么导致变化发生?

现在我们知道变化检测的全部内容,我们可能想知道,何时会发生这样的变化? Angular什么时候知道它必须更新视图?好吧,我们来看看下面的代码

@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个方面引起:

  1. 事件: click,input,submit...
  2. XHR: 从远程服务器获取数据
  3. 定时器:setTimeout(),setInterval()

事实证明,这三件事有一些共同之处。你能说出来吗? ......正确!它们都是异步的.

为什么你认为这很重要?嗯......因为事实证明,当Angular真正对更新视图感兴趣时,这些是唯一的情况。假设我们有一个Angular组件,当单击一个按钮时它会执行一个处理程序:

@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进行变化检测?

  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+变化检测的基础,下一篇重点讲一下变化检测策略.

       

angular 2+ 变化检测系列一(基础概念)的更多相关文章

  1. .NET技术面试题系列(1) 基础概念

    这是.NET技术面试题系列第一篇,今天主要分享基础概念. 1.简述 private. protected. public.internal 修饰符的访问权限 private : 私有成员, 在类的内部 ...

  2. 快速入门系列--WCF--01基础概念

    转眼微软的WCF已走过十个年头,它是微软通信框架的集大成者,将之前微软所有的通信框架进行了整合,提供了统一的应用方式.记得从自己最开始做MFC时,就使用过Named Pipe命名管道,之后做Winfo ...

  3. angular 2+ 变化检测系列三(Zone.js在Angular中的应用)

    在系列一中,我们提到Zone.js,Zones是一种执行上下文,它允许我们设置钩子函数在我们的异步任务的开始位置和结束位置,Angular正是利用了这一特性从而实现了变更检测. Zones.js非常适 ...

  4. 快速入门系列--TSQL-01基础概念

    作为一名程序员,对于SQL的使用算是基础中的基础,虽然也写了很多年的SQL,但常常还是记不清一些常见的命令,故而通过一篇博文巩固相关的记忆,并把T-SQL本身的一些新特性再进行一次学习. 首先回顾基础 ...

  5. angular 2+ 变化检测系列二(检测策略)

    我们将创建一个简单的MovieApp来显示有关一部电影的信息.这个应用程序将只包含两个组件:显示有关电影的信息的MovieComponent和包含执行某些操作按钮的电影引用的AppComponent. ...

  6. zabbix学习系列之基础概念

    触发器 概念 "监控项"仅负责收集数据,而通常收集数据的目的还包括在某指标对应的数据超出合理范围时给相关人员发送警告信息,"触发器"正式英语为监控项所收集的数据 ...

  7. QUARTZ系列之一-基础概念(Scheduler/Job/JobDetail/Trigger)

    摘抄自quartz官方文档: The key interfaces of the Quartz API are: Scheduler - the main API for interacting wi ...

  8. 理解 angular2 基础概念和结构 ----angular2系列(二)

    前言: angular2官方将框架按以下结构划分: Module Component Template Metadata Data Binding Directive Service Dependen ...

  9. MongoDB入门系列(一):基础概念和安装

    概述 MongoDB是目前非常流行的一种非关系型数据库,作为入门系列的第一篇本篇文章主要介绍Mongdb的基础概念知识包括命名规则.数据类型.功能以及安装等. 环境: OS:Windows Versi ...

随机推荐

  1. MVC 中Delete 方法报错问题解决方案

    最开始前台ajax提交时代码 function Del(id) { $.ajax({ type: "GET", url: "/Test/Delete", dat ...

  2. VB中的冒号——bug

    关于VB中的冒号,给许多人的印象都是:“一行可书写几句语句”.这么说是对的,但是有一种情况是不对的,那就是在条件语句中.这也是做一个VB项目升级的时候遇到,因为这个问题我查了好长时间程序,一直在找VB ...

  3. css3时钟

    参考资料: 奇舞团: http://www.75team.com/archives/851 DEMO:demo 截图: 代码: <!DOCTYPE html> <html lang= ...

  4. Android获取本机号码及运营商

    import android.content.Context; import android.telephony.TelephonyManager; import android.util.Log; ...

  5. Kafka中文官方文档

    参见链接:http://orchome.com/kafka/index

  6. springboot 文件上传大小配置

    转自:https://blog.csdn.net/shi0299/article/details/69525848 springboot上传文件大小的配置有两种,一种是设置在配置文件里只有两行代码,一 ...

  7. Stetho简化Android调试(一)

    在开发 Android 应用的时候,有时候我们需要查看数据库.SharePreference等.通常的做法是把相关文件pull出来,而前提必须是手机得root.如果没有root,那就只能通过打印或其它 ...

  8. Oracle11g 体系结构

    一:Oracle11g 体系结构 二:逻辑储存结构 二.1:数据块(data blocks) ----通过 v$parameter数据字典来查询oracle标准数据块的大小. SYS@orcl> ...

  9. Confluence 6 找到在创建 XML 备份的时候出现的错误

    错误可能是因为数据库突然不可访问而产生.如果你在你的日志中看到了错误  'Couldn't backup database data' ,这个指南将会帮助你更正这个错误.我们强烈推荐你备份 Confl ...

  10. 基于 Confluence 6 数据中心的 SAML 单点登录设置你的身份提供者

    如果你希望 Confluence 提供 SSO,将需要将 Confluence 添加到你的 IdP 中.一些后续的步骤将会与你的 IdP 有关,但是你通常需要: 在你的 IdP 中定义一个 'appl ...