简介
在ng的生态中scope处于一个核心的地位,ng对外宣称的双向绑定的底层其实就是scope实现的,本章主要对scope的watch机制、继承性以及事件的实现作下分析。
监听
1. $watch
// $watch: function(watchExp,listener,objectEquality) 使用过angular的会经常这上面这样的代码,俗称“手动”添加监听,其他的一些都是通过插值或者directive自动地添加监听,但是原理上都一样。
if (!isFunction(listener)) { if (!array) { // 之所以使用unshift不是push是因为在 $digest 中watchers循环是从后开始 // 返回unwatchFn,取消监听 从代码看 $watch 还是比较简单,主要就是将 watcher 保存到 $$watchers 数组中 2. $digest 当 scope 的值发生改变后,scope是不会自己去执行每个watcher的listenerFn,必须要有个通知,而发送这个通知的就是 $digest
整个 $digest 的源码差不多100行,主体逻辑集中在【脏值检查循环】(dirty check loop) 中, 循环后也有些次要的代码,如 postDigestQueue 的处理等就不作详细分析了。 脏值检查循环,意思就是说只要还有一个 watcher 的值存在更新那么就要运行一轮检查,直到没有值更新为止,当然为了减少不必要的检查作了一些优化。 代码: // 脏值检查循环开始 // asyncQueue 循环省略 traverseScopesLoop: } // 这段有点绕,其实就是实现深度优先遍历 // break traverseScopesLoop 直接到这边 // 判断是不是还处在脏值循环中,并且已经超过最大检查次数 ttl默认10 } while (dirty || asyncQueue.length); // 循环结束 上述代码中存在3层循环 第一层判断 dirty,如果有脏值那么继续循环
第二层判断 scope 是否遍历完毕,代码翻译了下,虽然还是绕但是能看懂
第三层循环scope的 watchers // ... 省略
3. $evalAsync
$evalAsync用于延迟执行,源码如下: 通过判断是否已经有 dirty check 在运行,或者已经有人触发过$evalAsync $browser.defer(function() { 如果不是使用defer,那么 scope.$evalAsync(fn1); // 这样的结果是 上节 $digest 中省略了了async 的内容,位于第一层循环中 简单易懂,弹出asyncTask进行执行。 不过这边有个细节,为什么这么设置呢?原因如下,假如在某次循环中执行到watchX时新加入1个asyncTask,此时会设置 lastDirtyWatch=watchX,恰好该task执行会导致watchX后续的一个watch执行出新值,如果没有下面的代码,那么下个循环到 lastDirtyWatch (watchX)时便跳出循环,并且此时dirty==false。 lastDirtyWatch = null;
还有这边还有一个细节,为什么在第一层循环呢?因为具有继承关系的scope其 $$asyncQueue 是公用的,都是挂载在root上,故不需要在下一层的scope层中执行。 scope具有继承性,如 $parentScope,$childScope 两个scope,当调用 $childScope.fn 时如果 $childScope 中没有 fn 这个方法,那么就是去 $parentScope上查找该方法。 这样一层层往上查找直到找到需要的属性。这个特性是利用 javascirpt 的原型继承的特点实现。 源码: 代码还算清楚,主要的细节是哪些属性需要独立,哪些需要基础下来。 最重要的代码: 就这样实现了继承。
var self = this; 跟 $wathc 类似,也是存放到数组 -- namedListeners。 还有不一样的地方就是该scope和所有parent都保存了一个事件的统计数,广播事件时有用,后续分析。
$emit 是向上广播事件。源码: // emit是向上的传播方式 event.currentScope = null; return event; 3.3 $broadcast $broadcast 是向内传播,即向child传播,源码: // 检查是否已经取消监听了 try { // 在digest中已经有过了 event.currentScope = null; 其他逻辑比较简单,就是在深度遍历的那段代码比较绕,其实跟digest中的一样,就是多了在路径上判断是否有监听,current.$$listenerCount[name],从上面$on的代码可知,只要路径上存在child有监听,那么该路径头也是有数字的,相反如果没有说明该路径上所有child都没有监听事件。 传播路径: Root>[A>[a1,a2],B>[b1,b2>[c1,c2],b3]] Root > A > a1 > a2 > B > b1 > b2 > c1 > c2 > b3
4. $watchCollection
expect($scope.dataCount).toEqual(4); expect($scope.dataCount).toEqual(4); $scope.names.pop(); expect($scope.dataCount).toEqual(3);
// 根据返回的changeDetected判断是否变化 // 通过此方法调用真正的listener,作为代理 } return this.$watch(changeDetector,$watchCollectionAction); 主脉络就是上面截取的部分代码,下面主要分析 $watchCollectionInterceptor 和 $watchCollectionAction
if (!isObject(newValue)) { newLength = newValue.length; if (oldLength !== newLength) { bothNaN = (oldItem !== oldItem) && (newItem !== newItem); } 1). 当值为undefined时直接返回。 2). 当值为普通基本类型时 直接判断是否相等。 3). 当值为类数组 (即存在 length 属性,并且 value[i] 也成立称为类数组),先没有初始化先初始化oldValue 然后比较数组长度,不等的话记为已变化 changeDetected++ 再进行逐个比较 4). 当值为object时,类似上面进行初始化处理 接下来的处理比较有技巧,但凡发现 newValue 多的新字段,就在oldLength 加1,这样 oldLength 只加不减,很容易发现 newValue 中是否有新字段出现,最后把 oldValue中多出来的字段也就是 newValue 中删除的字段给移除就结束了。
代码还是比较简单,就是调用 listenerFn,初次调用时 oldValue == newValue,为了效率和内存判断了下 listener是否需要oldValue参数 $apply 最后调用 $rootScope.$digest(),所以很多书上建议使用 $digest() ,而不是调用 $apply(),效率要高点。 主要逻辑都在$parse 属于语法解析功能,后续单独分析。var unwatch = $scope.$watch('aa',function () {},isEqual);
var listenFn = compileToFn(listener || noop,'listener');
watcher.fn = function(newVal,oldVal,scope) {listenFn(scope);};
}
array = scope.$$watchers = [];
}
// 为了使得新加入的watcher也能在当次循环中执行所以放到队列最前
array.unshift(watcher);
return function deregisterWatch() {
arrayRemove(array,watcher);
lastDirtyWatch = null;
};
}
do {
dirty = false;
current = target;
do {
if ((watchers = current.$$watchers)) {
length = watchers.length;
while (length--) {
try {
watch = watchers[length];
if (watch) {
// 作更新判断,是否有值更新,分解如下
// value = watch.get(current),last = watch.last
// value !== last 如果成立,则判断是否需要作值判断 watch.eq?equals(value,last)
// 如果不是值相等判断,则判断 NaN的情况,即 NaN !== NaN
if ((value = watch.get(current)) !== (last = watch.last) &&
!(watch.eq
? equals(value,last)
: (typeof value === 'number' && typeof last === 'number'
&& isNaN(value) && isNaN(last)))) {
dirty = true;
// 记录这个循环中哪个watch发生改变
lastDirtyWatch = watch;
// 缓存last值
watch.last = watch.eq ? copy(value,null) : value;
// 执行listenerFn(newValue,lastValue,scope)
// 如果第一次执行,那么 lastValue 也设置为newValue
watch.fn(value,((last === initWatchVal) ? value : last),current); // ... watchLog 省略
if (watch.get.$$unwatch) stableWatchesCandidates.push({watch: watch,array: watchers});
}
// 这边就是减少watcher的优化
// 如果上个循环最后一个更新的watch没有改变,即本轮也没有新的有更新的watch
// 那么说明整个watches已经稳定不会有更新,本轮循环就此结束,剩下的watch就不用检查了
else if (watch === lastDirtyWatch) {
dirty = false;
break traverseScopesLoop;
}
}
} catch (e) {
clearPhase();
$exceptionHandler(e);
}
}
// A->[B->D,C->E]
// 执行顺序 A,B,D,C,E
// 每次优先获取第一个child,如果没有那么获取nextSibling兄弟,如果连兄弟都没了,那么后退到上一层并且判断该层是否有兄弟,没有的话继续上退,直到退到开始的scope,这时next==null,所以会退出scopes的循环
if (!(next = (current.$$childHead ||
(current !== target && current.$$nextSibling)))) {
while(current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
} while ((current = next));
if((dirty || asyncQueue.length) && !(ttl--)) {
clearPhase();
throw $rootScopeMinErr('infdig','{0} $digest() iterations reached. Aborting!\n' +
'Watchers fired in the last 5 iterations: {1}',TTL,toJson(watchLog));
}
}
//...
});
}
scope.$evalAsync(fn2);
// $digest() > fn1 > $digest() > fn2
// 但是实际需要达到的效果:$digest() > fn1 > fn22. 继承性
child = new Scope();
child.$root = this.$root;
// isolate 的 asyncQueue 及 postDigestQueue 也都是公用root的,其他独立
child.$$asyncQueue = this.$$asyncQueue;
child.$$postDigestQueue = this.$$postDigestQueue;
} else {
if (!this.$$childScopeClass) {
this.$$childScopeClass = function() {
// 这里可以看出哪些属性是隔离独有的,如$$watchers,这样就独立监听了,
this.$$watchers = this.$$nextSibling =
this.$$childHead = this.$$childTail = null;
this.$$listeners = {};
this.$$listenerCount = {};
this.$id = nextUid();
this.$$childScopeClass = null;
};
this.$$childScopeClass.prototype = this;
}
child = new this.$$childScopeClass();
}
// 设置各种父子,兄弟关系,很乱!
child['this'] = child;
child.$parent = this;
child.$$prevSibling = this.$$childTail;
if (this.$$childHead) {
this.$$childTail.$$nextSibling = child;
this.$$childTail = child;
} else {
this.$$childHead = this.$$childTail = child;
}
return child;
}
this.$$childScopeClass.prototype = this;
3. 事件机制
do {
if (!current.$$listenerCount[name]) {
current.$$listenerCount[name] = 0;
}
current.$$listenerCount[name]++;
} while ((current = current.$parent));
return function() {
namedListeners[indexOf(namedListeners,listener)] = null;
decrementListenerCount(self,1,name);
};
}
namedListeners = scope.$$listeners[name] || empty;
event.currentScope = scope;
for (i=0,length=namedListeners.length; i<length; i++) {
// 当监听remove以后,不会从数组中删除,而是设置为null,所以需要判断
if (!namedListeners[i]) {
namedListeners.splice(i,1);
i--;
length--;
continue;
}
try {
namedListeners[i].apply(null,listenerArgs);
} catch (e) {
$exceptionHandler(e);
}
}
// 停止传播时return
if (stopPropagation) {
event.currentScope = null;
return event;
}
scope = scope.$parent;
} while (scope);
}
event.currentScope = current;
listeners = current.$$listeners[name] || [];
for (i=0,length = listeners.length; i<length; i++) {
if (!listeners[i]) {
listeners.splice(i,1);
i--;
length--;
continue;
}
listeners[i].apply(null,listenerArgs);
} catch(e) {
$exceptionHandler(e);
}
}
if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
(current !== target && current.$$nextSibling)))) {
while(current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
}
return event;
}
$scope.dataCount = newNames.length;
});
$scope.$digest();
$scope.$digest();
function $watchCollectionInterceptor(_value) {
// ...
return changeDetected;
}
function $watchCollectionAction() {
}
if (oldValue !== newValue) {
oldValue = newValue;
changeDetected++;
}
} else if (isArrayLike(newValue)) {
if (oldValue !== internalArray) {
oldValue = internalArray;
oldLength = oldValue.length = 0;
changeDetected++;
}
changeDetected++;
oldValue.length = oldLength = newLength;
}
for (var i = 0; i < newLength; i++) {
oldItem = oldValue[i];
newItem = newValue[i];
if (!bothNaN && (oldItem !== newItem)) {
changeDetected++;
oldValue[i] = newItem;
}
}
} else {
if (oldValue !== internalObject) {
oldValue = internalObject = {};
oldLength = 0;
changeDetected++;
}
newLength = 0;
for (key in newValue) {
if (hasOwnProperty.call(newValue,key)) {
newLength++;
newItem = newValue[key];
oldItem = oldValue[key];if (key in oldValue) {
bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
if (!bothNaN && (oldItem !== newItem)) {
changeDetected++;
oldValue[key] = newItem;
}
} else {
oldLength++;
oldValue[key] = newItem;
changeDetected++;
}
}
if (oldLength > newLength) {
changeDetected++;
for (key in oldValue) {
if (!hasOwnProperty.call(newValue,key)) {
oldLength--;
delete oldValue[key];
}
}
}
}
return changeDetected;
}
if (!bothNaN && (oldItem !== newItem)) {
changeDetected++;
oldValue[i] = newItem;
}
}
bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
if (!bothNaN && (oldItem !== newItem)) {
changeDetected++;
oldValue[key] = newItem;
}
} else {
oldLength++;
oldValue[key] = newItem;
changeDetected++;
}
}
}
if (oldLength > newLength) {
changeDetected++;
for (key in oldValue) {
if (!hasOwnProperty.call(newValue,key)) {
oldLength--;
delete oldValue[key];
}
}
}
// 如果需要就进行复制
if (trackVeryOldValue) {
if (!isObject(newValue)) {
veryOldValue = newValue;
} else if (isArrayLike(newValue)) {
veryOldValue = new Array(newValue.length);
for (var i = 0; i < newValue.length; i++) {
veryOldValue[i] = newValue[i];
}
} else {
veryOldValue = {};
for (var key in newValue) {
if (hasOwnProperty.call(newValue,key)) {
veryOldValue[key] = newValue[key];
}
}
}
}
}