AngularJS RootScope 源码分析
预备知识:Provider 中的$get 属性
说起这个$get属性,是每个系统provider都有的,主要是先保存要实例化的函数体,等待instanceinjector.invoke
的时候来调用,因为$get的代码比较多,所以先上要讲的那部分,大家可以注意到了,在$get上面有一个digestTtl
方法
this.digestTtl = function(value) {
if (arguments.length) {
TTL = value;
}
return TTL;
};
这个是用来修改系统默认的dirty check
次数的,默认是10次,通过在config
里引用rootscope
provider,可以调用这个方法传递不同的值来修改ttl(short for Time To Live)
下面来看下$get中的scope构造函数
function Scope() {
this.$id = nextUid();
this.$$phase = this.$parent this.$$watchers =
this.$$nextSibling this.$$prevSibling this.$$childHead this.$$childTail = null;
this['this'] this.$root = this;
this.$$destroyed false;
this.$$asyncQueue = [];
this.$$postDigestQueue this.$$listeners = {};
this.$$listenerCount this.$$isolateBindings = {};
}
可以看到在构造函数里定义了很多属性,我们来一一说明一下
- $id,通过nextUid方法来生成一个唯一的标识
- $$phase,这是一个状态标识,一般在
dirty check
时用到,表明现在在哪个阶段
- $parent,代表自己的上级scope属性
- $$watchers,保存scope变量当前所有的监控数据,是一个数组
- $$nextSibling,下一个兄弟scope属性
- $$prevSibling,前一个兄弟scope属性
- $$childHead,第一个子级scope属性
- $$childTail,最后一个子级scope属性
- $$destroyed,表示是否被销毁
- $$asyncQueue,代表异步操作的数组
- $$postDigestQueue,代表一个在
dirty check
之后执行的数组
- $$listeners,代表scope变量当前所有的监听数据,是一个数组
- $$listenerCount,暂无
- $$isolateBindings,暂无
通过这段代码,可以看出,系统默认会创建根作用域,并作为$rootScope
provider实例返回.
var $rootScope new Scope();
return $rootScope;
创建子级作用域是通过$new
方法
$new: function(isolate) {
var ChildScope,
child;
if (isolate) {
child new Scope();
child.$root this.$root;
// ensure that there is just one async queue per $rootScope and its children
child.$$asyncQueue this.$$asyncQueue;
child.$$postDigestQueue this.$$postDigestQueue;
} else {
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
if (!this.$$childScopeClass) {
this.$$childScopeClass function() {
=
null;
= {};
= nextUid();
null;
};
this.$$childScopeClass.prototype this;
}
child new this.$$childScopeClass();
}
child[= child;
child.$parent this;
child.$$prevSibling this.$$childTail;
if (this.$$childHead) {
this.$$childTail.$$nextSibling = child;
= child;
} else {
= child;
}
return child;
}
通过分析上面的代码,可以得出
-
isolate标识来创建独立作用域,这个在创建指令,并且scope属性定义的情况下,会触发这种情况,还有几种别的特殊情况,假如是独立作用域的话,会多一个$root属性,这个默认是指向rootscope的
-
如果不是独立的作用域,则会生成一个内部的构造函数,把此构造函数的prototype指向当前scope实例
-
通用的操作就是,设置当前作用域的$$childTail,$$childTail.$$nextSibling,$$childHead,this.$$childTail为生成的子级作用域;设置子级域的$parent为当前作用域,$$prevSibling为当前作用域最后一个子级作用域
$watch
$watch
函数有三个参数,第一个是监控参数,可以是字符串或者函数,第二个是监听函数,第三个是代表是否深度监听,注意看这个代码
get = compileToFn(watchExp, 'watch')
这个compileToFn
函数其实是调用$parse
实例来分析监控参数,然后返回一个函数,这个会在dirty check
里用到,用来获取监控表达式的值。同时这里的get = compileToFn(watchExp,'watch')
返回的get是一个可执行的表达式函数
$watchfunction(watchExp, listener, objectEquality) {
var scope this,
get 'watch'),
array = scope.$$watchers,
watcher = {
fn: listener,
last: initWatchVal,
get: get,
exp: watchExp,
eq: !!objectEquality
};
lastDirtyWatch null;
// in the case user pass string,we need to compile it,do we really need this ?
!isFunction(listener)) {
var listenFn = compileToFn(listener || noop,33)">'listener');
watcher.fn function(newVal, oldVal, scope) {listenFn(scope);};
}
typeof watchExp == 'string' && get.constant) {
var originalFn = watcher.fn;
watcher.fn scope) {
originalFn.call(newVal, scope);
arrayRemove(array, watcher);
};
}
!array) {
array = scope.$$watchers = [];
}
// we use unshift since we use a while loop in $digest for speed.
// the while loop reads in reverse order.
array.unshift(watcher);
return function deregisterWatch() {
arrayRemove(array, watcher);
lastDirtyWatch null;
};
}
接着:
watcher !!objectEquality
};
初始化了一个watcher
对象,用来保存一些监听相关的信息,简单的说明一下
- fn,代表监听函数,当监控表达式新旧不相等时会执行此函数
- last,保存最后一次发生变化的监控表达式的值
- get,保存一个监控表达式对应的函数,目的是用来获取表达式的值然后用来进行新旧对比的
- exp,保存一个原始的监控表达式
- eq,保存
$watch
函数的第三个参数,表示是否进行深度比较
然后会检查传递进来的监听参数是否为函数,如果是一个有效的字符串,则通过parse
来解析生成一个函数,否则赋值为一个noop
占位函数,最后生成一个包装函数,函数体的内容就是执行刚才生成的监听函数,默认传递当前作用域.
接着会检查监控表达式是否为字符串并且执行表达式的constant
为true,代表这个字符串是一个常量,那么,系统在处理这种监听的时候,执行完一次监听函数之后就会删除这个$watch
.最后往当前作用域里的$$watchers
数组头中添加$watch
信息,注意这里的返回值,利用JS的闭包保留了当前的watcher
,然后返回一个函数,这个就是用来删除监听用的.
$Parse
$watch在底层很大程度上依赖于$parse
,同时也是Angular $ompile的核心依赖方法之一,parse.js
里就是$parse的全部代码它的核心是解析字符串,而且默认支持四则运算,运算符号的优先级处理,只是额外的增加了对变量的支持以及过滤器的支持.
this.$get = ['$filter',33)">'$sniffer',33)">'$log', function($filter, $sniffer, $log) {
$parSEOptions.csp = $sniffer.csp;
promiseWarning function promiseWarningFn(fullExp) {
!$parSEOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return;
promiseWarningCache[fullExp] true;
$log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' +
'Automatic unwrapping of promises in Angular expressions is deprecated.');
};
function(exp) {
var parsedExpression;
switch (typeof exp) {
case 'string':
if (cache.hasOwnProperty(exp)) {
return cache[exp];
}
var lexer new Lexer($parSEOptions);
var parser new Parser(lexer, $filter, $parSEOptions);
parsedExpression = parser.parse(exp, false);
if (exp !== 'hasOwnProperty') {
// Only cache the value if it's not going to mess up the cache object
// This is more performant that using Object.prototype.hasOwnProperty.call
cache[exp] = parsedExpression;
}
return parsedExpression;
'function':
return exp;
defaultreturn noop;
}
};
}];
可以看出,假如解析的是函数,则直接返回,是字符串的话,则需要进行parser.parse
方法,这里重点说下这个
通过阅读parse.js
文件,你会发现,这里有两个关键类
-
lexer,负责解析字符串,然后生成token
,有点类似编译原理中的词法分析器
-
parser,负责对lexer生成的生成执行表达式,其实就是返回一个执行函数
看这里
new
Lexer($parSEOptions);
$parSEOptions);
parsedExpression false);
第一句就是创建一个lexer实例,第二句是把lexer实例传给parser构造函数,然后生成parser实例,最后一句是调用parser.parse
生成执行表达式,实质是一个函数
现在转到parser.parse
里去
parsefunction (text, json) {
this.text = text;
//TODO(i): strip all the obsolte json stuff from this file
this.json = json;
this.tokens this.lexer.lex(text);
console.log(this.tokens);
if (json) {
// The extra level of aliasing is here,just in case the lexer misses something,so that
// we prevent any accidental execution in JSON.
this.assignment this.logicalOR;
this.functionCall =
this.fieldAccess this.objectIndex this.filterChain function() {
this.throwError('is not valid json', {text: text, index: 0});
};
}
var value = json ? this.primary() : this.statements();
this.tokens.length !== 0) {
'is an unexpected token', this.tokens[0]);
}
value.literal = !!value.literal;
value.constant !!value.constant;
return value;
}
视线移到这句this.tokens = this.lexer.lex(text)
,然后来看看lex
方法
lexfunction (text) {
= text;
this.index = 0;
this.ch undefined;
this.lastCh = ':'; // can start regexp
= [];
var token;
var json = [];
while (< this.text.length) {
this.text.charAt(this.index);
this.is('"\'')) {
this.readString(this.ch);
} else this.isNumber(this.ch) || '.') && this.peek())) {
this.readNumber();
} this.isIdent(this.ch)) {
this.readIdent();
// identifiers can only be if the preceding char was a { or,
this.was('{,') && json[0] === '{' &&
(token this.tokens[- 1])) {
token.json = token.text.indexOf(=== -1;
}
} '(){}[].,;:?')) {
this.tokens.push({
indexthis.index,
textthis.ch,
json: (':[,33)">'{[')) '}]:,')
});
'{[')) json.unshift(this.ch);
'}]')) json.shift();
this.index++;
} this.isWhitespace(++;
continue;
} else {
var ch2 + this.peek();
var ch3 = ch2 this.peek(2);
var fn = OPERATORS[this.ch];
var fn2 = OPERATORS[ch2];
var fn3 = OPERATORS[ch3];
if (fn3) {
this.tokens.push({indextext: ch3, fn: fn3});
+= 3;
} if (fn2) {
: ch2,102)">: fn2});
2;
} if (fn) {
this.tokens.push({
indextextfn: fn,
json'[,:') '+-'))
});
1;
} 'Unexpected next character ',102)">+ 1);
}
}
this.ch;
}
return this.tokens;
}
这里我们假如传进的字符串是1+2
,通常我们分析源码的时候,碰到代码复杂的地方,我们可以简单化处理,因为逻辑都一样,只是情况不一样罢了.
上面的代码主要就是分析传入到lex
内的字符串,以一个while
loop开始,然后依次检查当前字符是否是数字,是否是变量标识等,假如是数字的话,则转到readNumber
方法,这里以1+2
为例,当前ch
是1
,然后跳到readNumber
方法
readNumberfunction() {
var number '';
var start this.index;
this.text.length) {
var ch = lowercase(this.index));
if (ch '.' this.isNumber(ch)) {
number += ch;
} var peekCh this.peek();
'e' this.isExpOperator(peekCh)) {
number += ch;
} this.isExpOperator(ch) &&
peekCh this.isNumber(peekCh) &&
number.charAt(number.length 1) 'e') {
number &&
(!peekCh || this.isNumber(peekCh)) 'e') {
'Invalid exponent');
} break;
}
}
++;
}
number 1 * number;
this.tokens.push({
index: start,
text: number,
json: true,
fnfunction() { return number; }
});
}
上面的代码就是检查从当前index
开始的整个数字,包括带小数点的情况,检查完毕之后跳出loop,当前index
向前进一个,以待以后检查后续字符串,最后保存到lex实例的token
数组中,这里的fn
属性就是以后执行时用到的,244)">return number是利用了JS的闭包特性,number
其实就是检查时外层的number
变量值.以1+2
为例,这时index
应该停在+
这里,在lex
的while loop
中,+
检查会跳到最后一个else
里,这里有一个对象比较关键,244)">OPERATORS,它保存着所有运算符所对应的动作,比如这里的+
,对应的动作是
'+':function(self, locals, a,b){
a=a(self, locals); b=b(self, locals);
if (isDefined(a)) {
if (isDefined(b)) {
return a + b;
}
return a;
}
return isDefined(b)?b:undefined;}
大家注意了,这里有4个参数,可以先透露一下,第一个是传的是当前上下文对象,比喻当前scope
实例,这个是为了获取字符串中的变量值,第二个参数是本地变量,是传递给函数当入参用的,基本用不到,最后两个参是关键,244)">+是二元运算符,所以a代表左侧运算值,b代表右侧运算值.
最后解析完+
之后,index
停在了2
的位置,跟1
一样,也是返回一个fn
属性也是一个返回当前数字的函数.
当解析完整个1+2
字符串后,lex
返回的是token
数组,这个即可传递给parse
来处理,来看看
this.statements();
默认json是false
,所以会跳到this.statements()
,这里将会生成执行语句.
statementsvar statements while (true) {
> 0 && this.peek('}',33)">')',33)">';',33)">']'))
statements.push(this.filterChain());
this.expect(';')) {
// optimize for the common case where there is only one statement.
// TODO(size): maybe we should not support multiple statements?
return (statements.length === 1)
? statements[0]
locals) {
var value;
for (var i 0; i < statements.length; i++) {
var statement = statements[i];
if (statement) {
value = statement(self, locals);
}
}
return value;
};
}
}
}
代码以一个无限loop
的while
开始,语句分析的时候是有运算符优先级的,默认的顺序是,这里以函数名为排序
filterChain<expression<assignment<ternary<logicalOR<logicalAND<equality<relational<additive<multiplicative<unary<primary
中文翻译下就是这样的
过滤函数<一般表达式<赋值语句<三元运算<逻辑or<逻辑and<比较运算<关系运算<加减法运算<乘法运算<一元运算,最后则默认取第一个token
的fn
属性
这里以1+2
的token
为例,这里会用到parse
的expect
方法,244)">expect会用到peek
方法
peekfunction(e1, e2, e3, e4) {
0) {
var token 0];
var t = token.text;
if (t === e1 || t === e2 === e3 === e4 ||
(!e1 !e2 !e3 !e4)) {
return token;
}
}
return false;
},
expecte4){
this.peek(e1, e4);
if (token) {
!token.json) {
token);
}
this.tokens.shift();
return token;
}
false;
}
expect
方法传空就是默认从token
数组中弹出第一个token
,数组数量减1
1+2的执行语句最后会定位到加法运算那里additive
additivevar left this.multiplicative();
var token;
while ((token '+','-'))) {
left this.binaryFn(left, token.fn,0); font-weight:bold">this.multiplicative());
}
return left;
}
最后返回一个二元操作的函数binaryFn
binaryFnfunction(left, fn, right) {
return extend(locals) {
return fn(self, left, right);
}, {
constant:left.constant && right.constant
});
}
这个函数参数里的left
,244)">right对应的'1','2'两个fn
属性,即是
function(){ return number;}
fn函数对应additive
方法中+
号对应fn
最后
生成执行表达式
函数,也就是
filterChain
返回的
left
值,被
push
到
statements
方法中的
statements
数组中,仔细看
statements
方法的返回值,假如表达式数组长度为1,则返回第一个执行表达式,否则返回一个包装的
函数,里面是一个
loop
,不断的执行表达式,只返回最后一个表达式的值
return value;
}
好了,说完了生成执行表达式,其实parse
的任务已经完成了,现在只需要把这个作为parse
provider的返回值了.
$eval
这个$eval
也是挺方便的函数,假如你想直接在程序里执行一个字符串的话,那么可以这么用
$scope.name '2';
$scope.$eval('1+name'); // ==> 会输出12
大家来看看它的函数体
return $parse(expr)(locals);
其实就是通过parse
来解析成一个执行表达式函数,然后传递当前作用域以及额外的参数,返回这个执行表达式函数的值
$evalAsync
evalAsync
函数的作用就是延迟执行表达式,并且执行完不管是否异常,触发dirty check
.
!$rootScope.$$phase !$rootScope.$$asyncQueue.length) {
$browser.defer(function() {
if ($rootScope.$$asyncQueue.length) {
$rootScope.$digest();
}
});
}
this.$$asyncQueue.push({scopeexpression: expr});
可以看到当前作用域内部有一个$$asyncQueue
异步队列,保存着所有需要延迟执行的表达式,此处的表达式可以是字符串或者函数,因为这个表达式最终会调用$eval
方法,注意这里调用了$browser
服务的defer
方法,从ng->browser.js
源码里可以看到,其实这里就是调用setTimeout
来实现的.
self.defer function(fn, delay) {
var timeoutId;
outstandingRequestCount++;
timeoutId = setTimeout(function() {
delete pendingDeferIds[timeoutId];
completeOutstandingRequest(fn);
}, delay || 0);
pendingDeferIds[timeoutId] true;
return timeoutId;
};
上面的代码主要是延迟执行函数,另外pendingDeferIds
对象保存所有setTimeout
返回的id,244)">self.defer.cancel这里可以取消执行延迟执行.
$postDigest
这个方法跟evalAsync
不同的时,它不会主动触发digest
方法,只是往postDigestQueue
队列中增加执行表达式,它会在digest
体内最后执行,相当于在触发dirty check
之后,可以执行别的一些逻辑.
this.$$postDigestQueue.push(fn);
$digest
digest方法是dirty check
的核心,主要思路是先执行$$asyncQueue
队列中的表达式,然后开启一个loop
来的执行所有的watch
里的监听函数,前提是前后两次的值是否不相等,假如ttl
超过系统默认值(在1.5.x版本中 ttl = 10),则dirth check
结束,最后执行$$postDigestQueue
队列里的表达式.
$digestfunction() {
var watch, value, last,
watchers,
asyncQueue this.$$asyncQueue,
postDigestQueue this.$$postDigestQueue,
length,
dirty, ttl = TTL,
next, current, target watchLog = [],
logIdx, logMsg, asyncTask;
beginPhase('$digest');
lastDirtyWatch null;
do { // "while dirty" loop
dirty false;
current = target;
while(asyncQueue.length) {
try {
asyncTask = asyncQueue.shift();
asyncTask.scope.$eval(asyncTask.expression);
} catch (e) {
clearPhase();
$exceptionHandler(e);
}
lastDirtyWatch null;
}
traverseScopesLoop// "traverse the scopes" loop
if ((watchers = current.$$watchers)) {
// process our watches
length = watchers.length;
while (length--) {
try {
watch = watchers[length];
// Most common watches are on primitives,in which case we can short
// circuit it with === operator,only when === fails do we use .equals
if (watch) {
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;
lastDirtyWatch = watch;
watch.last = watch.eq ? copy(value) : value;
watch.fn(value, ((last === initWatchVal) ? value : last), current);
if (ttl < 5) {
logIdx 4 - ttl;
!watchLog[logIdx]) watchLog[logIdx] = [];
logMsg = (isFunction(watch.exp))
? 'fn: ' + (watch.exp.name || watch.exp.toString())
: watch.exp;
logMsg += '; newVal: ' + toJson(value) '; oldVal: ' + toJson(last);
watchLog[logIdx].push(logMsg);
}
} if (watch === lastDirtyWatch) {
// If the most recently dirty watcher is now clean,short circuit since the remaining watchers
// have already been tested.
dirty false;
break traverseScopesLoop;
}
}
} catch (e) {
clearPhase();
$exceptionHandler(e);
}
}
}
// Insanity Warning: scope depth-first traversal
// yes,this code is a bit crazy,but it works and we have tests to prove it!
// this piece should be kept in sync with the traversal in $broadcast
!(next = (current.$$childHead ||
(current !== target && current.$$nextSibling)))) {
while(current = current.$$nextSibling)) {
current = current.$parent;
}
}
} while ((current = next));
// `break traverseScopesLoop;` takes us to here
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));
}
} while (dirty || asyncQueue.length);
clearPhase();
while(postDigestQueue.length) {
try {
postDigestQueue.shift()();
} catch (e) {
$exceptionHandler(e);
}
}
}
通过上面的代码,可以看出,核心就是两个loop,外loop保证所有的model都能检测到,内loop则是真实的检测每个watch
,244)">watch.get就是计算监控表达式的值,这个用来跟旧值进行对比,假如不相等,则执行监听函数
注意这里的watch.eq
这是是否深度检查的标识,244)">equals方法是angular.js
里的公共方法,用来深度对比两个对象,这里的不相等有一个例外,那就是NaN ===NaN
,因为这个永远都是!(watch.eq
last)
'number'
isNaN(last)))
比较完之后,把新值传给watch.last
,然后执行watch.fn
也就是监听函数,传递三个参数,分别是:最新计算的值,上次计算的值(假如是第一次的话,则传递新值),最后一个参数是当前作用域实例,这里有一个设置外loop
的条件值,那就是dirty = true
,也就是说只要内loop执行了一次性能,不过超过ttl
设置的值后,244)">dirty check
会强制关闭,并抛出异常
--)) {
clearPhase();
+
TTL, toJson(watchLog));
}
这里的watchLog
日志对象是在内loop里,当ttl
低于5的时候开始记录的
5) {
logIdx - ttl;
= [];
logMsg = (isFunction(watch.exp))
|| watch.exp.toString())
: watch.exp;
logMsg + toJson(last);
watchLog[logIdx].push(logMsg);
}
当检查完一个作用域内的所有watch
之后,则开始深度遍历当前作用域的子级或者父级
// Insanity Warning: scope depth-first traversal
// this piece should be kept in sync with the traversal in $broadcast
||
(current && current.$$nextSibling)))) {
= current.$$nextSibling)) {
current = current.$parent;
}
}
上面的代码其实就是不断的查找当前作用域的子级,没有子级,则开始查找兄弟节点,最后查找它的父级节点,是一个深度遍历查找.只要next
有值,则内loop则一直执行
= next))
不过内loop也有跳出的情况,那就是当前watch
跟最后一次检查的watch
相等时就退出内loop.
=== lastDirtyWatch) {
// have already been tested.
dirty break traverseScopesLoop;
}
注意这个内loop同时也是一个label(标签)语句,这个可以在loop中执行跳出操作就像上面的break
正常执行完两个loop之后,清除当前的阶段标识clearPhase();
,然后开始执行postDigestQueue
队列里的表达式.
while(postDigestQueue.length) {
try {
postDigestQueue.shift()();
} catch (e) {
$exceptionHandler(e);
}
}
接下来说说,用的也比较多的$apply
方法
$apply
这个方法一般用在,不在ng的上下文中执行js代码的情况,比如原生的DOM事件中执行想改变ng中某些model的值,这个时候就要使用$apply
方法了
$applyfunction(expr) {
try {
beginPhase('$apply');
this.$eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
clearPhase();
try {
$rootScope.$digest();
} catch (e) {
$exceptionHandler(e);
throw e;
}
}
}
代码中,首先让当前阶段标识为$apply
,这个可以防止使用$apply
方法时检查是否已经在这个阶段了,然后就是执行$digest
方法,来使ng中的M或者VM改变.
接下来说说scope
中event
模块,它的api跟一般的event
事件模块比较像,提供有$on
,244)">$emit,244)">$broadcast,这三个很实用的方法
$on
这个方法是用来定义事件的,这里用到了两个实例变量$$listeners
,244)">$$listenerCount,分别用来保存事件,以及事件数量计数
$onfunction(name, listener) {
var namedListeners this.$$listeners[name];
!namedListeners) {
this.$$listeners[name] = namedListeners = [];
}
namedListeners.push(listener);
var current this;
do {
!current.$$listenerCount[name]) {
current.$$listenerCount[name] 0;
}
current.$$listenerCount[name]++;
} = current.$parent));
var self function() {
namedListeners[indexOf(namedListeners, listener)] null;
decrementListenerCount(self, 1, name);
};
}
分析上面的代码,可以看出每当定义一个事件的时候,都会向$$listeners
对象中添加以name
为key的属性,值就是事件执行函数,注意这里有个事件计数,只要有父级,则也给父级的$$listenerCount
添加以+1
,这个$$listenerCount
会在广播事件的时候用到,最后这个方法返回一个取消事件的函数,先设置$$listeners
中以name
为key的值为null
,然后调用decrementListenerCount
来使该事件计数-1
.
$emit
这个方法是用来触发$on
定义的事件,原理就是loop$$listeners
属性,检查是否有值,有的话,则执行,然后依次往上检查父级,这个方法有点类似冒泡执行事件.
$emitargs) {
var empty namedListeners,
scope stopPropagation false,
event = {
name: name,
targetScope: scope,
stopPropagationfunction() {stopPropagation true;},
preventDefaultfunction() {
event.defaultPrevented true;
},
defaultPreventedfalse
},
listenerArgs = concat([event], arguments,102)">1),
i, length;
do {
namedListeners = scope.$$listeners[name] || empty;
event.currentScope = scope;
for (i=0, length=namedListeners.length; i<length; i++) {
// if listeners were deregistered,defragment the array
!namedListeners[i]) {
namedListeners.splice(i,102)">1);
i--;
length--;
continue;
}
try {
//allow all listeners attached to the current scope to run
namedListeners[i].apply(null, listenerArgs);
} catch (e) {
$exceptionHandler(e);
}
}
//if any listener on the current scope stops propagation,prevent bubbling
if (stopPropagation) return event;
//traverse upwards
scope = scope.$parent;
} while (scope);
return event;
}
上面的代码比较简单,首先定义一个事件参数,然后开启一个loop,只要scope
有值,则一直执行,这个方法的事件链是一直向上传递的,不过当在事件函数执行stopPropagation
方法,就会停止向上传递事件.
$broadcast
这个是$emit
的升级版,广播事件,即能向上传递,也能向下传递,还能平级传递,核心原理就是利用深度遍历当前作用域
$broadcastargs) {
var target current = target,
next event = {
nametargetScope: target,
preventDefaultfunction() {
event.defaultPrevented true;
},
defaultPreventedfalse
},
listenerArgs listeners, i, length;
//down while you can,then up and next sibling or up and next sibling until back at root
= next)) {
event.currentScope = current;
listeners = current.$$listeners[name] || [];
length = listeners.length; i++) {
!listeners[i]) {
listeners.splice(i,102)">1);
i--;
length--;
continue;
}
try {
listeners[i].apply(listenerArgs);
} catch(e) {
$exceptionHandler(e);
}
}
// Insanity Warning: scope depth-first traversal
// this piece should be kept in sync with the traversal in $digest
// (though it differs due to having the extra check for $$listenerCount)
= ((current.$$listenerCount[name] && current.$$childHead) ||
(current && current.$$nextSibling)))) {
= current.$$nextSibling)) {
current = current.$parent;
}
}
}
return event;
}
代码跟$emit
差不多,只是跟它不同的时,这个是不断的取next
值,而next
的值则是通过深度遍历它的子级节点,兄弟节点,父级节点,依次查找可用的以name
为key的事件.注意这里的注释,跟$digest
里的差不多,都是通过深度遍历查找,所以$broadcast
方法也不能常用,性能不是很理想
$destroy
这个方法是用来销毁当前作用域,代码主要是清空当前作用域内的一些实例属性,以免执行digest
,244)">$broadcast时会关联到
$destroyfunction() {
// we can't destroy the root scope or a scope that has been already destroyed
this.$$destroyed) return;
var parent this.$parent;
this.$broadcast('$destroy');
this === $rootScope) return;
forEach(this.$$listenerCount, bind(decrementListenerCount,0); font-weight:bold">this));
// sever all the references to parent scopes (after this cleanup,the current scope should
// not be retained by any of our references and should be eligible for garbage collection)
if (parent.$$childHead == this) parent.$$childHead this.$$nextSibling;
if (parent.$$childTail this) parent.$$childTail this.$$prevSibling;
this.$$prevSibling) this.$$prevSibling.$$nextSibling this.$$nextSibling) this.$$nextSibling.$$prevSibling this.$$prevSibling;
// All of the code below is bogus code that works around V8's memory leak via optimized code
// and inline caches.
//
// see:
// - https://code.google.com/p/v8/issues/detail?id=2073#c26
// - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
// - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
=
null;
// don't reset these to null in case some async task tries to register a listener/watch/task
= [];
// prevent NPEs since these methods have references to properties we nulled out
this.$destroy this.$digest this.$apply = noop;
this.$on this.$watch return noop; };
}
代码比较简单,先是通过foreach
来清空$$listenerCount
实例属性,然后再设置$parent
,244)">$$nextSibling,244)">$$prevSibling,244)">$$childHead,244)">$$childTail,244)">$root为$$watchers
,244)">$$asyncQueue,244)">$$postDigestQueue,最后就是重罢方法为noop
占位函数