发表于: 2017-07-13 22:25:39
1 1091
如何理解ANGULAR的脏检查?
小课堂【武汉第170期】
分享人:庄引
目录
1.背景介绍
2.知识剖析
3.常见问题
4.解决方案
5.编码实战
6.扩展思考
7.参考文献
8.更多讨论
1.背景介绍
$WATCH 对象
Angular 每一个绑定到UI的数据,就会有一个 $watch 对象。 这个对象包含三个参数
watch = {
name:'', //当前的watch 对象 观测的数据名
getNewValue:function($scope){ //得到新值
...
return newValue;
},
listener:function(newValue,oldValue){ // 当数据发生改变时需要执行的操作
...
}
}
每当我们将数据绑定到 UI 上,AAngular 就会向你的 watchList 上插入一个 $watch。 比如:
<span>{{user}}</span>
<span>{{password}}</span>
这就会插入两个$watch 对象。之后,开始脏检查。只有先理解了Angular的双向数据绑定,才能透彻理解脏检查 。 Angular实现了双向数据绑定。即界面的操作能实时反馈到数据,数据的更改也能在界面呈现。 界面到数据的更改,是由 UI 事件,ajax请求,或者timeout 等回调操作, 而数据到界面的呈现则是由脏检查来做 。
2.知识剖析
双向数据绑定
看下面的例子
<div ng-app="myApp" ng-controller="myCtrl">
<span ng-bind="counter"></span>
<button ng-click="counter=counter+1">increase</button>
</div>
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
$scope.counter = 0;
});
毫无疑问,我每点击一次button,counter就会+1,因为点击事件,将couter+1,而后触发了脏检查,又将新值2 返回给了界面. 这就是一个简单的双向数据绑定的流程. 但是就只有这么简单吗?
看下面的代码
'use strict';
var app = angular.module('app', []);
app.directive('myclick', function () {
return function (scope, element, attr) {
element.on('click', function () {
scope.data++;
console.log(scope.data)
})
}
})
app.controller('appController', function ($scope) {
$scope.data = 0;
});
<div ng-app="app">
<div ng-controller="appController">
<span>{{data}}</span>
<button myclick="">increase</button>
</div>
</div>
点击后,毫无反应 ?
当在 console.log(scope.data) 之后添加 scope.$digest(); ? 也可以使用$apply () 也可以(后面会讲解 )
为什们呢? 假设没有AngularJS,要让我们自己实现这个类似的功能,该怎么做呢?
<button ng-click="increase">increase</button>
<button ng-click="decrease">decrease</button>
<span ng-bind="data"></span>
<script src="app.js"></script>
window.onload = function() {
'use strict';
var scope = {
increase: function() {
this.data++;
},
decrease: function decrease() {
this.data--;
},
data: 0
}
function bind() {
var list = document.querySelectorAll('[ng-click]');
for (var i = 0, l = list.length; i < l; i++) {
list[i].onclick = (function(index) {
return function() {
var func = this.getAttribute('ng-click');
scope[func](scope);
apply();
}
})(i);
}
}
// apply
function apply() {
var list = document.querySelectorAll('[ng-bind]');
for (var i = 0, l = list.length; i < l; i++) {
var bindData = list[i].getAttribute('ng-bind');
list[i].innerHTML = scope[bindData];
}
}
bind();
apply();
}
可以看到我们没有直接使用DOM的onclick方法,而是搞了一个ng-click, 然后在bind里面把这个ng-click对应的函数拿出来,绑定到onclick的事件处理函数中。 为什么要这样呢?因为数据虽然变更了,但是还没有往界面上填充,我们需要在此做一些附加操作。 另外,由于双向绑定机制,在DOM操作中,虽然更新了数据的值,但是并没有立即反映到界面上, 而是通过 apply() 来反映到界面上,从而完成职责的分离,可以认为是单一职责模式了。 在真正的Angular中,ng-click 封装了click,然后调用一次 apply 函数,把数据呈现到界面上 在Angular 的apply函数中,这里先进行脏检测, 看 oldValue 和 newVlue 是否相等,如果不相等,那么讲newValue 反馈到界面上, 通过如果通过 $watch 注册了 listener事件,那么就会调用该事件。
3.常见问题
1.ANGULAR 脏检查(DIRTY CHECK)更新机制
2.ANGULAR 脏检查的优缺点
4.解决方案
Angular 视图模板里, 读取数据有一个叫 $scope 的上下文, 可以就认为它是一个存储数据的JS对象, 里面放了各种视图模板需要访问的数据/函数。$scope 里面的值可以自由更改 (比如说在 Angular 的控制器(Controller)里面)。 比如说有这样一个非常简单的 Angular 应用 (模板中引用了 $scope 里的 title 数据, 同时绑定了 onclick 事件修改 title 数据):
3.常见问题
1.ANGULAR 脏检查(DIRTY CHECK)更新机制
2.ANGULAR 脏检查的优缺点
4.解决方案
3.常见问题
1.ANGULAR 脏检查(DIRTY CHECK)更新机制
2.ANGULAR 脏检查的优缺点
4.解
3.常见问题
1.ANGULAR 脏检查(DIRTY CHECK)更新机制
2.ANGULAR 脏检查的优缺点
4.解决方案
<script>
var testApp = angular.module('testApp', []);
testApp.controller('TestController', function ($scope) {
$scope.title = 'test title';
$scope.onDIVClick = function () {
$scope.title = 'another title';
};
});
</script>
<div ng-app="testApp" ng-controller="TestController" ng-click="onDIVClick()">
{{title}} <!-- 显示为: test title -->
</div>
然后点击 div 触发事件, 就会调用 $scope.onDIVClick, 修改了 title 数据, 然后视图就更新了 (内容变为了 another title)。 这一步DOM更新是怎么做到的? 仅仅只是更新了一个JavaScript对象的属性值, 但ES5版本下的JavaScript中并没有监听对象属性变更的方法, 没办法通过事件触发更新; 修改的属性名是任意的, 更不可能提前设置对象的 setter 嵌入数据更新操作。 答案是, Angular 不监听数据更新, 数据发生任何改变时 Angular 都不理睬, 它只是找了一个恰当的时机, 遍历所有的DOM更新方法, 从被修改过任意次的 $scope 数据中尝试更新DO (这里这个"恰当的时机"就是click事件处理结束时)。
* 简单理解,一次脏检查就是调用一次 $apply() 或者 $digest(),将数据中最新的数据呈现在界面上。
* 每次 UI 事件变更,ajax 还有 timeout 都会触发 $apply()。
2.ANGULAR 脏检查的优缺点
不断触发脏检查是不是一种好的方式? 有很多人认为,这样对性能的损耗很大, 不如 setter 和 getter 的观察者模式。 如果界面上某个文本绑定在循环中的变量,会怎样?在脏检测的机制下, 这个过程毫无压力,在后台完成所有数据变更,然后整体应用到界面上。 这时候,基于setter的机制就惨了,除非它也是像Angular这样把批量操作延时到一次更新,否则性能会更低。 所以说,两种不同的监控方式,各有其优缺点,最好的办法是了解各自使用方式的差异,考虑出它们性能的差异所在, 在不同的业务场景中,避开最容易造成性能瓶颈的用法。
5.编码实战
6.扩展思考
触发ANGULAR的脏检查机制(DIRTY-CHECKING)?
angular 的脏检查机制(dirty-checking), 常见的误解认为angular是定时轮询去检查model是否变更。 angular并不存在定时脏检测。 ngular对常用的dom事件,xhr事件等做了封装, 在里面触发进入angular的digest流程。 在digest流程里面, 会从rootscope开始遍历, 检查所有的watcher。 因此,angular只有在指定事件触发后,才进入$digest cycle:
DOM事件,譬如用户输入文本,点击按钮等。(ng-click)
XHR响应事件 ($http)
浏览器Location变更事件 ($location)
Timer事件($timeout, $interval)
执行$digest()或$apply()
ANGULAR、REACT、VUE 数据更新
不同框架的具体实现思想差别还是挺大的。 像 React, 并不引入任何数据观察机制, 而是利用虚拟节点树 (Virtual DOM), 做到每次都重绘整个界面 (同时保证最小的DOM差异更新); Vue 大致是通过JS setter, 将某个数据的更新绑定到对应的DOM上 (于是区别于 Angular, Vue 并不会每次遍历所有的 watcher)。 而 Angular 的实现核心: 脏检查(Dirty Check)。
7.参考文献
参考一:Angular 脏检查机制研究|Blog - Yunfei
8.更多讨论
脏检查有哪些坑? 怎么避免踩坑?
脏检查的内部实现?
评论