angular双向数据绑定原理
- 从UI到数据:UI事件,ajax请求,timeout等。
- 从数据到UI:脏检查
脏检查
1. 添加监听器$watcher
为scope中渲染在页面中的每个数据添加监听器$watcher,当添加$watcher
的数据发生变化时,调用所有watcher对应的listener,执行数据变化后的操作。
function $scope() {
this.$$watchList = [];
}
$scope.prototype.$watch = function (name,getNewValue,listener) {
var watch = {
name: name,//scope中的数据名称
getNewValue: getNewValue,//数据的当前值
listener: listener || function () {} //数据变化时执行的操作
};
this.$$watchList.push(watch);
};
2. 数据改变时调用所有watcher
中的listener
在$digest
里面调用所有watcher
。
$scope.prototype.$digest = function () {
var list = this.$$watchList;
for (var i = 0,l = list.length; i < l; i++) {
var watch = list[i];
var newValue = watch.getNewValue();
var oldValue = watch.last;//第一次执行时oldValue为undefined
if (newValue != oldValue) {
watch.listener(newValue,oldValue);
}
watch.last = newValue;
}
};
此时当newValue != oldValue
时便执行$watcher对应的listener.
测试:
var scope = new $scope();
scope.hello = 5;
scope.$watch('hello',function () {
return scope.hello;
},function (newValue,oldValue) {
console.log('newValue: ' + newValue + ';oldValue:' + oldValue);
});
scope.$digest();
scope.hello = 10;
scope.$digest();
scope.hello = 20;
scope.$digest();
scope.hello = 20;
scope.$digest();
scope.hello = 26;
scope.$digest();
newValue: 5;oldValue:undefined
newValue: 10;oldValue:5
newValue: 20;oldValue:10
newValue: 26;oldValue:20
3. 持续监听
然而当在另一个watcher
中改变其他watcher
中的值时,上面的digest
不能监听到变化:
var scope = new $scope();
scope.hello = 5;
scope.hello2 = 15;
scope.$watch('hello',oldValue) {
console.log('newValue: ' + newValue + ';oldValue:' + oldValue);
});
scope.$watch('hello2',function () {
return scope.hello2;
},oldValue) {
scope.hello = 10;//这里的改变并未检测到
console.log('newValue: ' + newValue + ';oldValue:' + oldValue);
});
scope.$digest();
newValue: 5;oldValue:undefined
newValue: 15;oldValue:undefined
因此,在scope
中值发生变化时需要持续进行digest
,在digest
中设置所有数据是否为脏的标志dirty
,即每次执行digest
,若有数据发生变化,就将dirty
置为true
,若dirty
为true
继续执行digest
。
$scope.prototype.$$digestOnce = function () {
var dirty;
var list = this.$$watchList;
for (var i = 0,l = list.length; i < l; i++) {
var watch = list[i];
var newValue = watch.getNewValue();
var oldValue = watch.last;
if (newValue !== oldValue) {
watch.listener(newValue,oldValue);
// 因为listener操作,已经检查过的数据可能变脏
dirty = true;
}
watch.last = newValue;
}
return dirty;
};
$scope.prototype.$digest = function () {
var dirty = true;
while (dirty) {
dirty = this.$$digestOnce();
}
};
var scope = new $scope();
scope.hello1 = 5;
scope.hello2 = 10;
scope.$watch('hello1',function () {
return scope.hello1;
},oldValue) {
console.log('newValue:' + newValue + '~~~~' + 'oldValue:' + oldValue);
});
scope.$watch('hello2',oldValue) {
scope.hello1 = 20;//这里的值被监听到
console.log('newValue:' + newValue + '~~~~' + 'oldValue:' + oldValue);
});
scope.$digest();
newValue:5~~~~oldValue:undefined
newValue:10~~~~oldValue:undefined
newValue:20~~~~oldValue:5
4. 防止持续运行digest
陷入死循环
然而当在watcher
中互相改变对方的值,则会无限次digest
,此时陷入死循环,因此在,digest
中设置最多执行次数:
$scope.prototype.$digest = function() {
var dirty = true;
var checkTimes = 0;
var maxTime = 10;
while(dirty) {
dirty = this.$$digestOnce();
checkTimes++;
if(checkTimes > maxTime && dirty){
console.log("数据持续脏");
throw new Error("检测超过10次");
}
}
};