angularJs 进阶
$watch
$apply
$digest
angular context
dirty checking
####angular context
- 浏览器事件循环
浏览器一直等待事件触发,当在页面触发事件(点击等),事件的回调函数便开始在javascript 解释器中执行,对dom进行操作,回调函数完成后,页面变出现了变化
- angular 为了扩展浏览器循环会生成 angular context 执行环境
####$watch 队列
- 每当我们在view上绑定一个一些东西的时候
每一个绑定到了UI上的数据,就会生成一个$watch
app.controller('MainCtrl', function($scope) {
$scope.foo = "Foo";
$scope.world = "World";
});
app.controller('MainCtrl', function($scope) {
Hello,
});
上面的例子生成了1个$watch
- $watch是什么时候生成的呢?
模板加载完毕时(linking阶段)Angular解释器会寻找每个directive,然后生成每个需要的$watch。
####$digest
- 当浏览器接收到可以被angular context处理的事件时, 就会执行$digest循环
- 两个更小的循环组合:
- evalAsync队列
- watch队列
过程(dirty checking
):
- $digest 遍历 $watch list
- 访问每个$watch 你的值是多少,你变化了吗
- 遍历一遍后,对访问结果进行检查
- 如果发现有至少一个更新过,这个循环就会再次触发,直到所有的$watch都没有变化。
app.controller('MainCtrl', function() {
$scope.name = "Foo";
$scope.changeFoo = function() {
$scope.name = "Bar";
}
});
<button ng-click="changeFoo()">Change the name</button>
- 我们按下按钮
- 浏览器接收到一个事件,进入angular context
- $digest循环开始执行,查询每个$watch是否变化。
- 由于监视$scope.name的$watch报告了变化,它会强制再执行一次$digest循环。
- 新的$digest循环没有检测到变化。
- 浏览器拿回控制权,更新与$scope.name新值相应部分的DOM。
每一个进入angular context的事件都会执行一个$digest循环
####$apply
- 谁决定什么事件进入angular context,而哪些又不进入呢?$apply!
- 如果当事件触发时,你调用$apply,它会进入angular context,如果没有调用就不会进入。
- Angular做了!点击带有ng-click的元素时,事件就会被封装到一个$apply调用。
- 如果你有一个ng-model=”foo”的输入框,然后你敲一个f,事件就会这样调用$apply(“foo = ‘f’;”)。
为什么我的jQuery不会更新我绑定的东西呢?因为jQuery没有调用$apply,事件没有进入angular context,$digest循环永远没有执行。
app.directive('clickable', function() {
return {
restrict: "E",
scope: {
foo: '=',
bar: '='
},
template: '<ul style="background-color: lightblue"><li></li><li></li></ul>',
link: function(scope, element, attrs) {
element.bind('click', function() {
scope.foo++;
scope.bar++;
});
}
}
});
app.controller('MainCtrl', function($scope) {
$scope.foo = 0;
$scope.bar = 0;
});
那我们点击元素的时候会发生什么呢?我们能看到更新吗?答案是否定的。因为点击事件是一个没有封装到$apply里面的常见的事件,这意味着我们会失去我们的计数吗?不会
真正的结果是:$scope确实改变了,但是没有强制$digest循环,监视foo 和bar的$watch没有执行。也就是说如果我们自己执行一次$apply那么这些$watch就会看见这些变化,然后根据需要更新DOM。
element.bind('click', function() {
scope.$apply(function() {
scope.foo++;
scope.bar++;
});
})
这样就可以了(手动调用apply)
####$watch扩展
- $watch默认是比较两个对象所引用的是否相同
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" };
$scope.updated = 0;
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
});
});
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" };
$scope.updated = 0;
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
}, true);
});