Angular2的双向数据绑定
什么是双向绑定
如图:
双向绑定机制维护了页面(View)与数据(Data)的一致性。如今,MVVM已经是前段流行框架必不可少的一部分。
Angular2中的双向绑定
双向绑定,也是Angular2的核心概念之一,Angular2的双向绑定是这样的:
- data=>view:数据绑定,模板语法是 []
- view=>data:事件绑定,模板语法是 ()
- Angular其实并没有一个双向绑定的实现,他的双向绑定就是数据绑定+事件绑定,模板语法是 [()] 。
Angular2官方给的例子:
<!--value是数据绑定,input是事件绑定-->
<input [value]="currentHero.name"
(input)="currentHero.name=$event.target.value"
>
<!--等价-->
<input [(ngModel)]="currentHero.name">
上面是input空间的双向绑定语法,很清楚的说明了双向绑定与两个单向绑定的关系。这里没有使用ngModule
语法,ngModule
语法内部实现与这个差不多。
事件绑定
- 用户操作出发DOM事件通知
- Angular监听到了通知,然后执行模板语法,上面的例子就是将input控件的输入值赋给了
currentHero.name
。
数据绑定
由于js语言并没有属性变化通知的机制,所以angular也不知道谁发生了变化,在什么时候变了。Angular的变化机制是:
上面的例子中input的数据绑定过程如下:
- 代码修改了
currentHero.name
的值。 - 触发整个组件树的变化检查。
- input显示了修改后的值。
数据何时变化
主要入下集中情况可能改变数据:
- 用户输入操作,比如点击,提交等。
- 请求服务端数据。
- 定时事件,比如
setTimeout
,setInterval
。
这几点有个共同点,就是他们都是异步的。也就是说,所有的异步操作是可能导致数据变化的根源因素。
如何通知变化
在Angularjs中是由代码$scope.$apply()
或者$scope.$digest
触发,而Angular2接入了ZoneJS
,由它监听了Angular所有的异步事件。ZoneJS重写了所有的异步API(所谓的猴子补丁,MonkeyPath)。ZoneJS会通知Angular可能有数据发生变化,需要检测更新。
变化检测原理 -- 脏检查
所谓脏检查就是存储所有变量的值,每当可能有变量发生变化需要检查时,就将所有变量的旧值跟新值进行比较,不相等就说明检测到变化,需要更新对应的视图。
AngularJS与Angular2变化检测的区别
Angularjs的变化检测机制也是脏检查,而Angular2的变化检测性能比Angularjs提升了很多。
Angular2
Angular的核心是组件化,组件的嵌套会使得最终形成一棵组件树。Angular的变化检测可以分组件进行,每个组件都有对应的变化检测器ChangeDetector
。可想而知,这些变化检测器也会构成一棵树。
另外,Angular的数据流是自顶而下的,从父组件到子组件单向流动。单向数据流向保证了高效、可预测的变化检测,尽管检查了负组件之后,自组件可能会改变父组件的数据使得父组件需要再次被检查,这是不被推荐的数据处理方式。在开发模式下,Angular会进行二次检查,如果出现上述情况,二次检查就会报错:ExpressionChangedAfterItHasBeenCheckedError
(关于这个问题的答案,可以在参考资料中找到)。而在生产环境中,脏检查只会执行一次。
Angularjs
相比之下,Angularjs采用的是双向数据流,错综复杂的数据流使得他不得不多次检查,使得数据最终趋向稳定。理论上,数据永远不可能稳定,Angularjs的策略是,脏检查超过10次就认定程序有问题。
变化检测优化
优化策略
有2个思路:
- OnPush策略:我知道我没变,别查我。
- 手动控制刷新:我变了,只查我。
变化检测策略 OnPush
Angular还让开发者拥有制定变化策略的能力。
export enum ChangeDetectionStrategy {
OnPush, // 表示变化检测对象的状态为`CheckOnce`
Default, // 表示变化检测对象的状态为`CheckAlways`
}
从ChangeDetectionStrategy
可以看到,Angular有两种变化检测策略。Default
是Angular默认的变化检测策略,也就是脏检查(只要有值发生变化,就全部检查)。开发者可以根据场景来设置更加高效的变化检测方式:OnPush
。OnPush
策略,就是只有当输入数据的引用发生变化或者有事件触发时,组件进行变化检测。
@Component({
template: `
<h2>{{vData.name}}</h2>
<span>{{vData.email}}</span>
`,
// 设置该组件的变化检测策略为onPush
changeDetection: ChangeDetectionStrategy.OnPush
})
class VCardCmp {
@Input() vData;
}
比如上面这个例子,当vData
的属性值发生变化的时候,这个组件不会发生变化检测,只有当vData
重新赋值的时候才会。一般,只接受输入的木偶子组件(dumb components)比较适合采用onPush
策略。
那什么时候只要对象的属性值发生变化,整个对象的引用就变了呢?不可变对象(Immutable Object)。当组件中的输入对象是不变量时,可采用onPush
变化检测策略,减少变化检测的频率。换个角度来说,为了更加智能地执行变化检测,可以在只接受输入的子组件中采用onPush
策略。
手动控制变化检测
Angular不仅可以让开发者设置变化检测策略,还可以让开发者获取变化检测对象引用ChangeDetectorRef
,手动去操作变化检测。变化检测对象引用给开发者提供的方法有以下几种:
markForCheck()
:将检查组件的所有父组件所有子组件,即使设置了变化检测策略为onPush
。detach()
:将变化检测对象脱离检测对象树,不再进行变化检查;结合detectChanges
可实现局部变化检测。(采用onPush
策略之后的组件detach()
无效)detectChanges()
:将检测该组件及其子组件,结合detach
可实现局部检测。checkNoChanges()
: 检测该组件及其子组件,如果有变化存在则报错,用于开发阶段二次验证变化已经完成。reattach()
:将脱离的变化检测对象重新链接到变化检测树上。
那么,如果是Observable的话,它会订阅所有的变量变化,只要在订阅回调函数中手动触发变化检测即可实现最小成本的检测(仍采用onPush
变化检测策略)。举个例子:
@Component({
template: '{{counter}}',
changeDetection: ChangeDetectionStrategy.OnPush
})
class CartBadgeCmp {
@Input() addItemStream:Observable<any>;
counter = 0;
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
this.addItemStream.subscribe(() => {
this.counter++; // 数据模型发生变化
this.cd.markForCheck(); // 手动触发检测
})
}
}
另外,当数据模型变化太过频繁,我们可自定义变化检测的时机。举个例子:
@Component({
template: `{{counter}}
<input type="check" (click)="toggle()">`,
})
class CartBadgeCmp {
counter = 0;
detectEnabled = false;
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
// 每10毫秒增加1
setInterval(()=>{this.counter++}, 10);
}
toggle(){
if( this.detectEnabled ){
this.cd.reattach(); // 链接上变化检测树
}
else{
this.cd.detach(); // 脱离变化检测树
}
}
}
总结
Angular与Angularjs都采用变化检测机制,前者优于后者主要体现在:
- 单项数据流动
- 以组件为单位维度独立进行检测
- 生产环境只进行一次检查
- 可自定义的变化检测策略:
Default
和onPush
- 可自定义的变化检测操作:
markForcheck()
、detectChanges()
、detach()
、reattach()
、checkNoChanges()
- 代码实现上的优化,据说采用了VM friendly的代码。
链接:https://www.jianshu.com/p/cee44e8831c9
Angular2的双向数据绑定的更多相关文章
- angular2在双向数据绑定时[(ngModel)]无法使用的问题
angular2在双向数据绑定时[(ngModel)]无法使用,出现的错误是: Can't bind to 'ngModel' since it isn't a known property of ' ...
- vue双向数据绑定原理探究(附demo)
昨天被导师叫去研究了一下vue的双向数据绑定原理...本来以为原理的东西都非常高深,没想到vue的双向绑定真的很好理解啊...自己动手写了一个. 传送门 双向绑定的思想 双向数据绑定的思想就是数据层与 ...
- 双向数据绑定(angular,vue)
最近github上插件项目更新了关于双向数据绑定的实现方式,关于angular和vue. angular众所周知是使用的脏检查($dirty).一开始大家会认为angular开启了类似setInter ...
- jQuery.my – 实时的复杂的双向数据绑定
jQuery.my 这个插件用于实时双向数据绑定.它发生变异给出的数据源对象,反映了用户与用户界面之间的相互作用.jQuery.my 提供了全面的验证,条件格式,复杂的依赖关系,运行形式结构操作. 马 ...
- Angular双向数据绑定MVVM以及基本模式分析
MVVM: angular的MVVM实现的是双向数据绑定,模型从服务器端抓取到数据,将数据通过控制器(controller)传递到视图(view)显示,视图数据发生变化时同样也会影响到模型数据的变化, ...
- 《AngularJS权威教程》中关于指令双向数据绑定的理解
在<AngularJS权威教程>中,自定义指令和DOM双向数据绑定有一个在线demo,网址:http://jsbin.com/IteNita/1/edit?html,js,output,具 ...
- Angular解决双向数据绑定
<!DOCTYPE html> <html ng-app="myApp1"><body><div ng-controller=" ...
- AngularJS入门心得2——何为双向数据绑定
前言:谁说Test工作比较轻松,最近在熟悉几个case,差点没疯.最近又是断断续续的看我的AngularJS,总觉得自己还是没有入门,可能是自己欠前端的东西太多了,看不了几行代码就有几个常用函数不熟悉 ...
- angularJs:双向数据绑定
示例1 <!DOCTYPE html> <html ng-app> <head> <meta charset="UTF-8" /> ...
随机推荐
- axios发送post请求[body-parser]--['Content-type': 'application/x-www-form-urlencoded']
const express = require('express') const axios = require('axios') const bodyParser = require('body-p ...
- CSS - 定位(position),难点
元素的定位属性主要包括定位模式和边偏移两部分. 1. 边偏移 边偏移属性 描述 top 顶端偏移量,定义元素相对于其父元素上边线的距离 bottom 底部偏移量,定义元素相对于其父元素下边线的距离 l ...
- 一个基础又很重要的知识点:JDBC原理(基本案例和面试知识点)
JDBC全称又叫做Java DataBase Connectivity,就是Java数据库连接,说白了就是用Java语言来操作数据库.这篇文章主要是对JDBC的原理进行讲解.不会专注于其使用.主要是理 ...
- ajax的分页查询
(1)先写个显示数据的页面,分页查询需要那几个部分呢? 1.首先是查询的文本框输入,还有查询按钮,那么就开始写代码吧 1 2 3 4 <div> <input type=" ...
- [Linux] day01——运维
开发和运维,本质都是提供一种服务. ---------------------------------(最终用户)1 应用2 中间件 服务平台 存储 架构 3 操作系统 硬件驱动4 计算机 网络设备 ...
- Scrapy 下载文件和图片
我们学习了从网页中爬取信息的方法,这只是爬虫最典型的一种应用,除此之外,下载文件也是实际应用中很常见的一种需求,例如使用爬虫爬取网站中的图片.视频.WORD文档.PDF文件.压缩包等. 1.Files ...
- 代理实现aop以及代理工厂实现增强
一.静态代理实现 1.接口(抽象主题) 2.接口的实现类(真实主题) 3.代理类(代理主题) 4.测试类: ApplicationContext context=new ClassPathXmlApp ...
- 报错google.protobuf.text_format.ParseError: 166:8 : Message type "object_detection.protos.RandomHorizontalFlip" has no field named "i".解决方法
运行python train.py --logtostderr --train_dir=training/ --pipeline_config_path=training/ssd_mobilenet_ ...
- oracle练习-day02
.查询员工表和部门表.查询出雇员的编号,姓名,部门的编号和名称,地址.查询出每个员工的上级领导.在上一个例子的基础上查询该员工的部门名称.在上一个例子的基础上查询员工工资等级和他的上级领导工资等级.查 ...
- Vmware tools变灰不能点击的问题
1. 挂载镜像文件,虚拟机->设置->硬件->CD/DVD.右边“连接”下面选择“使用IOS镜像文件”,浏览选择虚拟机包目录下面linux.iso 2. 挂载成功后,在虚拟机右下角c ...