Angular变化检测机制

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

在使用Angular进行开发中,我们常用到Angular中的绑定——模型到视图的输入绑定、视图到模型的输出绑定以及视图与模型的双向绑定。而这些绑定的值之所以能在视图与模型之间保持同步,正是得益于Angular中的变化检测。

变化检测是什么?

简单来说变化检测就是Angular用来检测视图与模型之间绑定的值是否发生了改变,当检测到模型中绑定的值发生改变时,则同步到视图上,反之,当检测到视图上绑定的值发生改变时,则回调对应的绑定函数。

什么情况下会引起变化检测?

变化检测的关键在于如何最小粒度地检测到绑定的值是否发生了改变,那么在什么情况下会导致这些绑定的值发生变化呢?我们可以看一下我们常用的几种场景:

@Component({
selector: 'my-app',
template: `
<h1>{{name}}</h1>
<button (click)="changeName()">change name</button>
`
})
export class MyApp {
name='Tom';
changeName(){
this.name='Jerry'
}
}

我们在视图上通过插值表达式绑定了MyApp中的name属性,当点击按钮时,改变了name属性的值,这时就导致了绑定的值发生了变化。再来看另外一种场景:

@Component({
selector: 'my-app',
template: `<h1>{{name}}</h1>`,
styleUrls: ['./app.component.css']
})
export class MyApp implements OnInit{
name='Tom';
constructor(private http:Http){}
ngOnInit(){
this.http.get('/contacts')
.map(res=>res.json)
.subscribe(name=>this.name=name);
}
}

我们在MyApp这个组件的生命周期钩子函数里向服务器端发送了一个Ajax请求,当这个请求返回结果时,同样会改变当前视图上绑定的name属性的值。类似的,我们还可能设定一些定时任务,这些定时任务也可能会改变与视图绑定的值:

@Component({
selector: 'my-app',
template: `<h1>{{name}}</h1>`,
styleUrls: ['./app.component.css']
})
export class MyApp implements OnInit{
name='Tom';
constructor(private http:Http){}
ngOnInit(){
setTimeout(()=>{
this.name='Jerry'
},1000);
}
}

其实,我们不难发现上述三种情况都有一个共同点,即这些导致绑定值发生改变的事件都是异步发生的。如果这些异步的事件在发生时能够通知到Angular框架,那么Angular框架就能及时的检测到变化。

那么Angular框架如何才能获知到这些异步事件的发生呢?我们不妨来看一看异步事件在JavaScript中执行的过程:

左边表示将要运行的代码,这里的stack表示JavaScript的运行栈,而webApi则是浏览器中提供的一些JavaScript的API,TaskQueue表示JavaScript中任务队列,因为JavaScript是单线程的,异步任务在任务队列中执行。

当上述代码在JavaScript中执行时,首先func1 进入运行栈,func1执行完毕后,setTimeout进入运行栈,执行setTimeout过程中将回调函数cb 加入到任务队列,然后setTimeout出栈,接着执行func2函数,func2函数执行完毕时,运行栈为空,接着任务队列中cb 进入运行栈得到执行。可以看出异步任务首先会进入任务队列,当运行栈中的同步任务都执行完毕时,异步任务进入运行栈得到执行。如果这些异步的任务执行前与执行后能提供一些钩子函数,通过这些钩子函数,Angular便能获知异步任务的执行。

事实上,Angular正是使用Zonejs(它描述JavaScript执行过程的上下文,可以在异步任务之间进行持久性传递,它类似于Java中的TLS)来做到的。Zonejs通过猴子补丁的方式,对webApi中的一些异步任务的API在运行时进行替换,替换后的API提供了一些钩子函数。

变化检测是个什么样的过程?

通过上面的介绍,我们大致明白了变化检测是如何被触发的,那么Angular中的变化检测是如何执行的呢?首先我们需要知道的是,对于每一个组件,都有一个对应的变化检测器;即每一个Component都对应有一个changeDetector,我们可以在Component中通过依赖注入来获取到changeDetector。而我们的多个Component是一个树状结构的组织,由于一个Component对应一个changeDetector,那么changeDetector之间同样是一个树状结构的组织。最后我们需要记住的一点是,每次变化检测都是从树根开始的。

枯燥无味的理论到此结束,下面通过一些例子来直观的感受一下。

Main.component.ts :
import {Component} from '@angular/core';
import {Actor} from './actor.model';
@Component({
selector: 'main',
template: `
<h1>MovieApp</h1>
<p>{{ slogan}}</p>
<button type="button" (click)="changeActorProperties()">Change Actor Properties
</button>
<button type="button" (click)="changeActorObject()">
ChangeActorObject
</button>
<movie [title]="title" [actor]="actor"></movie>`
})export class MainComponent {
slogan: string = 'Just movie information';
title: string = 'Terminator 1';
actor: Actor = new Actor('Arnold', 'Schwarzenegger');
changeActorProperties() {
this.actor.firstName = 'Nicholas';
this.actor.lastName = 'Cage';
}changeActorObject() {
this.actor = new Actor('Bruce', 'Willis');
}
}Movie.component.ts:
import {Component, Input} from '@angular/core';
import {Actor} from './actor.model';
@Component({
selector: 'movie',
styles: ['div{border: 1px solid black}'],
template: `
<div>
<h3>{{ title}}</h3>
<p>
<label>Actor:</label>
<span>{{actor.firstName}} {{actor.lastName}}</span>
</p>
</div>`
})
export class MovieComponent {
@Input() title: string;
@Input() actor: Actor;}

上述代码中,MainComponent通过<movie></movie> 标签嵌入了MovieComponent,从树状结构上来说,MainComponent是MovieComponent的根节点,而MovieComponent是MainComponent的叶子节点。当我们点击MainComponent的第一个button时,会回调到changeActorProperties方法,然后会触发变化检测的执行。首先变化检测从MainComponent开始:

  • 检测slogan 值是否发生了变化:没有发生变化

  • 检测 title 值是否发生了变化:没有发生变化

  • 检测 actor 值是否发生了变化:没有发生变化

你可能对于 actor的检测结果感到疑惑,不是明明改变了actor的属性值吗?实质上在对actor检测时只检测actor 本身的引用值是否发生了改变,改变actor的属性值并未改变actor 本身的引用,因此是没有发生变化。而当我们点击MainComponent的第二个button ,重新new了一个 actor ,这时变化检测才会检测到 actor发生了改变。

然后变化检测进入到叶子节点MovieComponent:

  • 检测title 值是否发生了改变:没有发生变化

  • 检测actor.firstName 是否发生了变化:发生了改变

  • 检测actor.lastName 是否发生了改变:发生了改变

因为MovieComponent再也没有了叶子节点,所以变化检测将更新DOM,同步视图与模型之间的变化。

看到这里你可能会想,这机制未免也有点太简单粗暴了吧,假如我的应用中有成百上千个Component,随便一个Component 触发了检测,那么都需要从根节点到叶子节点重新检测一遍。别着急,Angular 的开发团队已经考虑到了这个问题,上述的检测机制只是一种默认的检测机制,Angular还提供一种OnPush的检测机制,还是同样的例子,下面看一下OnPush检测机制是咋样的:

Main.component.ts :
import {Component, ChangeDetectionStrategy} from '@angular/core';
import {Actor} from './actor.model';
@Component({
selector: 'main',
template: `
<h1>MovieApp</h1>
<p>{{ slogan}}</p>
<button type="button" (click)="changeActorProperties()">
Change Actor Properties
</button>
<button type="button" (click)="changeActorObject()">
Change Actor Object
</button>
<movie [title]="title" [actor]="actor"></movie>`,
changeDetection:ChangeDetectionStrategy.OnPush
})export class MainComponent {
slogan: string = 'Just movie information';
title: string = 'Terminator 1';
actor: Actor = new Actor('Arnold', 'Schwarzenegger');
changeActorProperties() {
this.actor.firstName = 'Nicholas';
this.actor.lastName = 'Cage';
}
changeActorObject() {
this.actor = new Actor('Bruce', 'Willis');
}
}

与上面的代码相比,只在@Component中添加了:

changeDetection:ChangeDetectionStrategy.OnPush

即将检测机制设置为OnPush。同样的,当我们点击第一个button时,将会发生如下变化检测:

  • 检测slogan 值是否发生了变化:没有发生变化

  • 检测 title 值是否发生了变化:没有发生变化

  • 检测 actor 值是否发生了变化:没有发生变化

好,变化检测到此结束,不会再进入到 MovieComponent 中了。这正是OnPush与Default之间的差别:当检测到与子组件输入绑定的值没有发生改变时,变化检测就不会深入到子组件中去。那么当我们点击MainComponent中的第二个按钮时,由于改变了actor本身而不是它的属性值,那么就会检测到actor的变化,进而继续进入到MovieComponent 进行变化检测。所以,当你使用了OnPush检测机制时,在修改一个绑定值的属性时,要确保同时修改到了绑定值本身的引用。但是每次需要改变属性值的时候去new一个新的对象会使得代码很难看,并且有时候你难以保证你一定记得这么做,恩,immutable.js 你值得拥有!

另外一个问题就是,当我们使用OnPush并且输入绑定的是一个Observable对象时,怎么才能检测到当订阅的事件发生时引起的绑定的值的发生了改变呢?比如下面这个组件:

import {Component, Input, ChangeDetectionStrategy} from '@angular/core';
import {Observable} from "rxjs";
@Component({
selector: 'my-count',
template: `<span>{{count}}</span>`,
changeDetection:ChangeDetectionStrategy.OnPush
})export class CountComponent {
@Input() addItemStream: Observable<any>;
count=0;
ngOnInit(){
this.addItemStream.subscribe(()=>{
this.count++;
});
}
}

输入绑定 addItemStream 是一个Observable对象,Observable对象本身是不会变化的,只有当订阅的事件到达时,才会去修改count的值。如果使用OnPush 那么检测就不会进入到CountComponent。解决的办法很简单,只需在修改count的值后做一个标记(markForCheck),那么变化检测就会沿着CountComponent所在的树枝进行变化检测。

import {Component, Input, ChangeDetectionStrategy, ChangeDetectorRef} from '@angular/core';
import {Observable} from "rxjs";
@Component({
selector: 'my-count',
template: `<span>{{count}}</span>`,
changeDetection:ChangeDetectionStrategy.OnPush
})
export class CountComponent {
@Input() addItemStream: Observable<any>;
count=0;
constructor(private cd:ChangeDetectorRef){
}
ngOnInit(){
this.addItemStream.subscribe(()=>{
this.count++;
this.cd.markForCheck();
});
}
}

总结

总结来说,Angular中变化检测器是树型结构的组织,与组件树结构相对应,默认情况下,当一个组件引发了变化检测时,检测是从树根开始一直检测到树节点。当你设置某个组件的检测策略是 OnPush 时,如果该组件的输入绑定没有发生变化时,那么检测就不会进入到该组件。当组件树变的很庞大时,常用这种办法来提高应用的性能。

转载于:https://my.oschina.net/u/2285087/blog/1142820

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/289543.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nx二次开发c语言,NX二次开发-UFUN API函数编程基础

1.NXOpen C 的函数函数名称的约定NX Open C 共有2类名称约定&#xff1a;一个是标准的NX Open C 的函数名称约定&#xff1b;另一个是以前版本的原有的名称约定。1.标准名称约定【格式】UF__【说明】(1)UF:User Funciton的简写&#xff0c;表示该函数为NX Open C 函数。(2)&…

JMeter压力测试入门教程[图文]

Apache JMeter是Apache组 织开发的基于Java的压力测试工具。用于对软件做压力测试&#xff0c;它最初被设计用于Web应用测试但后来扩展到其他测试领域。 它可以用于测试静态和动态资源例如静态文件、Java小服务程序、CGI脚本、Java 对象、数据库&#xff0c; FTP服务器, 等等。…

.NET6之MiniAPI(二十九):UnitTest

MiniAPI的单元测试与asp.net web api的单元测试大体是相同的&#xff08;毕竟都是asp.net core&#xff09;&#xff0c;只是在小细节上有一些差异&#xff0c;文章中会说到这点。本文测试框架是XUnit&#xff0c;Mock框架是Moq&#xff0c;关于这两个框架和库的学习&#xff0…

vue data数据修改_Vue 超清晰思维导图(7张),详细知识点梳理!

Vue思维导图目录MVC与MVVM的区别Vue基本代码结构Vue指令Vue组件class和style动态绑定computed计算属性EventBusfilter过滤器方法Vue是一套构建用户界面的框架&#xff0c;只关注视图层&#xff0c;它不仅易于上手&#xff0c;还便于与第三方库或既有项目整合。&#xff08;Vue有…

界面连接数据库

1、获取本机的SQL Server服务器名 private void Form2_Load(object sender, EventArgs e){listBox1.Items.Clear();SQLDMO.Application SQLServer = new SQLDMO.Application();SQLDMO.NameList strServer = SQLServer.ListAvailableSQLServers();if (strServer.Count > 0){f…

Xamarin效果第二十篇之GIS中加载三维白模

在前面文章中简单玩了玩GIS的基本操作、Mark相关、AR和测距,今天再次分享一下N年前就像玩耍的效果;啥也不说了都在效果里:再来看看手机端的效果:1、关于效果我也是偶然见看到了别人实现:https://blog.csdn.net/arcgis_all/article/details/769991042、关于实现就是在三维场景图…

visa虚拟卡生成器_虚拟卡有哪些功能?赶紧了解一下

现在很多人都会有机会出国&#xff0c;或者是直接在国外生活&#xff0c;他们时不时的会到国内办理一些事情&#xff0c;而这个时候为了确保信用卡在使用的时候安全&#xff0c;有些人就会申请使用虚拟信用卡&#xff0c;这样不但可以解决跨国支付的麻烦&#xff0c;从而确保在…

android编程绘图,Android编程绘图操作之弧形绘制方法示例

本文实例讲述了Android编程绘图操作之弧形绘制方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;/*** 绘制弧形图案* description&#xff1a;* author ldm* date 2016-4-25 下午4:37:01*/public class ArcsActivity extends Activity {Overrideprotected void onCr…

BeetleX实现MessagePack和Protobuf消息控制器调用websocket服务详解

最近有用户问如何使用BeetleX封装一个基于Protobuf格式的websocket服务并支持控制器调用&#xff1b;其实BeetleX.FastHttpApi是支持Websocket服务和自定义数据格式的&#xff0c;但需要对组件有一定了解的情况才能进行扩展&#xff1b;接下来通过封装一个支持Protobuf和Messag…

vue 获取url地址的参数_2020年 vue常见面试问题总结(干货)!

1.什么是mvvm模式&#xff0c;谈谈你的理解&#xff1f; MVVM - Model View ViewModel&#xff0c;数据&#xff0c;视图&#xff0c;视图模型view 可以通过 事件绑定 的方式影响 model&#xff0c;model 可以通过 数据绑定 的形式影响到view&#xff0c;viewModel是把 model 和…

CSS 定位之绝对与相对

static,relative,absolute,fixed含义 static(静态定位):元素框正常生成。块级元素生成一个矩形框&#xff0c;作为文档流的的一部分&#xff0c;行内元素则会常见一个或多个行框&#xff0c;至于其父元素中。默认值。没有定位&#xff0c;元素出现在正常的流中&#xff08;忽略…

[第二篇]如何在ASP.Net Core的生产环境中使用OAuth保护swagger ui

在我上篇文章如何在ASP.Net Core的生产环境中保护swagger ui中&#xff0c;我们讨论了如何使用基本身份验证来保护 swagger ui。使用 OAuth 2.0 和 OpenIdConnect 进行保护随着应用程序越来越多地使用 OAuth 和 OpenIdConnect&#xff0c;应用程序很有可能使用 OAuth 和 OpenID…

python opencv 图像切割_【OpenCV+Python】图像的基本操作与算术运算

图像的基本操作在上个教程中&#xff0c;我们介绍了使用鼠标画笔的功能。本次教程&#xff0c;我们将要谈及OpenCV图像处理的基本操作。本次教程的所有操作基本上都和Numpy相关&#xff0c;而不是与OpenCV相关。要使用OpenCV编写更好的优化代码&#xff0c;需要Numpy的丰富知识…

光伏领跑者火热前行 可靠性护航“长跑”

随着第三批光伏领跑者申报标准的出台&#xff0c;在目前普通电站指标有可能缩水的情况下&#xff0c;2017年8-10GW的光伏领跑者项目又将成为各电站投资商争夺的“红海”。光伏领跑者在过去两年时间里为行业带来的变化有目共睹&#xff0c;从模式创新到电价下降&#xff0c;快速…

鸿蒙os系统被推送,鸿蒙来了!华为大规模推送鸿蒙OS系统,造成网站一度瘫痪...

千呼万唤始出来&#xff0c;期盼已久的手机鸿蒙OS系统终于迎来了大规模推送&#xff01;今年2月份在华为Mate X2折叠屏手机发布会上&#xff0c;华为就曾表示将在4月份开始大规模推送鸿蒙OS系统&#xff0c;4月27日通过测试申请的用户正式接到升级鸿蒙OS系统的通知&#xff0c;…

jps、jinfo、jstat、jstack、jmap、jconsole等命令简介

2019独角兽企业重金招聘Python工程师标准>>> JDK提供了几个很实用的工具&#xff0c;如下&#xff1a; jinfo&#xff1a;观察运行中的java程序的运行环境参数&#xff1a;参数包括Java System属性和JVM命令行参数&#xff0c;java class path等信息。命令格式&…

读《底层逻辑》

《底层逻辑》本书作者是刘润&#xff0c;他在得到上开设了课程《5 分钟商学院》&#xff0c;我也是在得到上知道的这本书&#xff0c;这本书在豆瓣上的评分不是很高&#xff0c;褒贬不一&#xff0c;不过我看着觉得挺好。看了一些豆瓣的评论&#xff0c;觉得这本书不好有这么几…

2016年:勒索病毒造成损失预估超过10亿美元

根据趋势科技公布的最新报告&#xff08;PDF&#xff09;&#xff0c;2016年是敲诈勒索软件频发的一年&#xff0c;同比增长752%&#xff0c;预测由Locky、Goldeneye等勒索病毒所造成的损失超过10亿美元。报告中同时指出企业和个人是勒索软件的重灾区&#xff0c;而且勒索病毒还…

python3.6字典有序_为什么从Python 3.6开始字典有序并效率更高

在Python 3.5&#xff08;含&#xff09;以前&#xff0c;字典是不能保证顺序的&#xff0c;键值对A先插入字典&#xff0c;键值对B后插入字典&#xff0c;但是当你打印字典的Keys列表时&#xff0c;你会发现B可能在A的前面。 但是从Python 3.6开始&#xff0c;字典是变成有顺序…

Linux的进程/线程间通信方式总结

2019独角兽企业重金招聘Python工程师标准>>> Linux系统中的进程间通信方式主要以下几种: 同一主机上的进程通信方式 * UNIX进程间通信方式: 包括管道(PIPE), 有名管道(FIFO), 和信号(Signal) * System V进程通信方式&#xff1a;包括信号量(Semaphore), 消息队列(Me…