首页 > 代码库 > Angularjs 源码分析2
Angularjs 源码分析2
本文主要分析RootScopeProvider和ParseProvider
RootScopeProvider简介
今天这个rootscope可是angularjs里面比较活跃的一个provider,大家可以理解为一个模型M或者VM,它主要负责与控制器或者指令进行数据交互.今天使用的源码跟上次分析的一样也是1.2.X系列,只不过这次用的是未压缩合并版的,方便大家阅读,可以在这里下载
从$get属性说起
说起这个$get属性,是每个系统provider都有的,主要是先保存要实例化的函数体,等待instanceinjector.invoke的时候来调用,因为$get的代码比较多,所以先上要讲的那部分,大家可以注意到了,在$get上面有一个digestTtl方法
1 2 3 4 5 6 | this .digestTtl = function (value) { if (arguments.length) { TTL = value; } return TTL; }; |
这个是用来修改系统默认的dirty check次数的,默认是10次,通过在config里引用rootscopeprovider,可以调用这个方法传递不同的值来修改ttl(short for Time To Live)
下面来看下$get中的scope构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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, 暂无
通过这段代码,可以看出,系统默认会创建根作用域,并作为$rootScopeprovider实例返回.
1 2 3 | var $rootScope = new Scope(); return $rootScope; |
创建子级作用域是通过$new方法,我们来看看.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | $ 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 () { 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; } |
通过分析上面的代码,可以得出
-
isolate标识来创建独立作用域,这个在创建指令,并且scope属性定义的情况下,会触发这种情况,还有几种别的特殊情况,假如是独立作用域的话,会多一个$root属性,这个默认是指向rootscope的
-
如果不是独立的作用域,则会生成一个内部的构造函数,把此构造函数的prototype指向当前scope实例
-
通用的操作就是,设置当前作用域的$$childTail,$$childTail.$$nextSibling,$$childHead,this.$$childTail为生成的子级作用域;设置子级域的$parent为当前作用域,$$prevSibling为当前作用域最后一个子级作用域
说完了创建作用域,再来说说$watch函数,这个比较关键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | $watch: function (watchExp, listener, objectEquality) { var scope = this , get = compileToFn(watchExp, ‘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 ? if (!isFunction(listener)) { var listenFn = compileToFn(listener || noop, ‘listener‘ ); watcher.fn = function (newVal, oldVal, scope) {listenFn(scope);}; } if ( typeof watchExp == ‘string‘ && get.constant) { var originalFn = watcher.fn; watcher.fn = function (newVal, oldVal, scope) { originalFn.call( this , newVal, oldVal, scope); arrayRemove(array, watcher); }; } if (!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 ; }; } |
$watch函数有三个参数,第一个是监控参数,可以是字符串或者函数,第二个是监听函数,第三个是代表是否深度监听,注意看这个代码
1 | get = compileToFn(watchExp, ‘watch‘ ) |
这个compileToFn函数其实是调用$parse实例来分析监控参数,然后返回一个函数,这个会在dirty check里用到,用来获取监控表达式的值,这个$parseprovider也是angularjs中用的比较多的,下面来重点的说下这个provider
$parse的代码比较长,在源码文件夹中的ng目录里,parse.js里就是$parse的全部代码,当你了解完parse的核心之后,这部份代码其实可以独立出来,做成自己的计算器程序也是可以的,因为它的核心就是解析字符串,而且默认支持四则运算,运算符号的优先级处理,只是额外的增加了对变量的支持以及过滤器的支持,想想,把这块代码放在模板引擎里也是可以的,说多了,让我们来一步一步的分析parse代码吧.
记住,不管是哪个provider,先看它的$get属性,所以我们先来看看$parse的$get吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | this .$get = [ ‘$filter‘ , ‘$sniffer‘ , ‘$log‘ , function ($filter, $sniffer, $log) { $parseOptions.csp = $sniffer.csp; promiseWarning = function promiseWarningFn(fullExp) { if (!$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.‘ ); }; return 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; case ‘ function ‘: return exp; default : return noop; } }; }]; |
可以看出,假如解析的是函数,则直接返回,是字符串的话,则需要进行parser.parse方法,这里重点说下这个
通过阅读parse.js文件,你会发现,这里有两个关键类
-
lexer, 负责解析字符串,然后生成token,有点类似编译原理中的词法分析器
-
parser, 负责对lexer生成的token,生成执行表达式,其实就是返回一个执行函数
看这里
1 2 3 | var lexer = new Lexer($parseOptions); var parser = new Parser(lexer, $filter, $parseOptions); parsedExpression = parser.parse(exp, false ); |
第一句就是创建一个lexer实例,第二句是把lexer实例传给parser构造函数,然后生成parser实例,最后一句是调用parser.parse生成执行表达式,实质是一个函数
现在转到parser.parse里去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | parse: function (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 = http://www.mamicode.com/json ? this .primary() : this .statements(); if ( this .tokens.length !== 0) { this .throwError( ‘is an unexpected token‘ , this .tokens[0]); } value.literal = !!value.literal; value.constant = !!value.constant; return value; } |
视线移到这句this.tokens = this.lexer.lex(text),然后来看看lex方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | lex: function (text) { this .text = text; this .index = 0; this .ch = undefined; this .lastCh = ‘:‘ ; // can start regexp this .tokens = []; var token; var json = []; while ( this .index < this .text.length) { this .ch = this .text.charAt( this .index); if ( this .is( ‘"\‘‘ )) { this .readString( this .ch); } else if ( this .isNumber( this .ch) || this .is( ‘.‘ ) && this .isNumber( this .peek())) { this .readNumber(); } else if ( this .isIdent( this .ch)) { this .readIdent(); // identifiers can only be if the preceding char was a { or , if ( this .was( ‘{,‘ ) && json[0] === ‘{‘ && (token = this .tokens[ this .tokens.length - 1])) { token.json = token.text.indexOf( ‘.‘ ) === -1; } } else if ( this .is( ‘(){}[].,;:?‘ )) { this .tokens.push({ index: this .index, text: this .ch, json: ( this .was( ‘:[,‘ ) && this .is( ‘{[‘ )) || this .is( ‘}]:,‘ ) }); if ( this .is( ‘{[‘ )) json.unshift( this .ch); if ( this .is( ‘}]‘ )) json.shift(); this .index++; } else if ( this .isWhitespace( this .ch)) { this .index++; continue ; } else { var ch2 = this .ch + 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({index: this .index, text: ch3, fn: fn3}); this .index += 3; } else if (fn2) { this .tokens.push({index: this .index, text: ch2, fn: fn2}); this .index += 2; } else if (fn) { this .tokens.push({ index: this .index, text: this .ch, fn: fn, json: ( this .was( ‘[,:‘ ) && this .is( ‘+-‘ )) }); this .index += 1; } else { this .throwError( ‘Unexpected next character ‘ , this .index, this .index + 1); } } this .lastCh = this .ch; } return this .tokens; } |
这里我们假如传进的字符串是1+2,通常我们分析源码的时候,碰到代码复杂的地方,我们可以简单化处理,因为逻辑都一样,只是情况不一样罢了.
上面的代码主要就是分析传入到lex内的字符串,以一个whileloop开始,然后依次检查当前字符是否是数字,是否是变量标识等,假如是数字的话,则转到readNumber方法,这里以1+2为例,当前ch是1,然后跳到readNumber方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | readNumber: function () { var number = ‘‘ ; var start = this .index; while ( this .index < this .text.length) { var ch = lowercase( this .text.charAt( this .index)); if (ch == ‘.‘ || this .isNumber(ch)) { number += ch; } else { var peekCh = this .peek(); if (ch == ‘e‘ && this .isExpOperator(peekCh)) { number += ch; } else if ( this .isExpOperator(ch) && peekCh && this .isNumber(peekCh) && number.charAt(number.length - 1) == ‘e‘ ) { number += ch; } else if ( this .isExpOperator(ch) && (!peekCh || ! this .isNumber(peekCh)) && number.charAt(number.length - 1) == ‘e‘ ) { this .throwError( ‘Invalid exponent‘ ); } else { break ; } } this .index++; } number = 1 * number; this .tokens.push({ index: start, text: number, json: true , fn: function () { return number; } }); } |
上面的代码就是检查从当前index开始的整个数字,包括带小数点的情况,检查完毕之后跳出loop,当前index向前进一个,以待以后检查后续字符串,最后保存到lex实例的token数组中,这里的fn属性就是以后执行时用到的,这里的return number是利用了JS的闭包特性,number其实就是检查时外层的number变量值.以1+2为例,这时index应该停在+这里,在lex的while loop中,+检查会跳到最后一个else里,这里有一个对象比较关键,OPERATORS,它保存着所有运算符所对应的动作,比如这里的+,对应的动作是
1 2 3 4 5 6 7 8 9 | ‘+‘ : 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实例,这个是为了获取字符串中的变量值,第二个参数是本地变量,是传递给函数当入参用的,基本用不到,最后两个参是关键,+是二元运算符,所以a代表左侧运算值,b代表右侧运算值.
最后解析完+之后,index停在了2的位置,跟1一样,也是返回一个token,fn属性也是一个返回当前数字的函数.
当解析完整个1+2字符串后,lex返回的是token数组,这个即可传递给parse来处理,来看看
1 | var value = http://www.mamicode.com/json ? this .primary() : this .statements(); |
默认json是false,所以会跳到this.statements(),这里将会生成执行语句.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | statements: function () { var statements = []; while ( true ) { if ( this .tokens.length > 0 && ! this .peek( ‘}‘ , ‘)‘ , ‘;‘ , ‘]‘ )) statements.push( this .filterChain()); if (! 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] : function (self, locals) { var value; for ( var i = 0; i < statements.length; i++) { var statement = statements[i]; if (statement) { value = http://www.mamicode.com/statement(self, locals); } } return value; }; } } } |
代码以一个无限loop的while开始,语句分析的时候是有运算符优先级的,默认的顺序是,这里以函数名为排序
filterChain
过滤函数<一般表达式<赋值语句<三元运算<逻辑or<逻辑and<比较运算<关系运算<加减法运算<乘法运算<一元运算,最后则默认取第一个token的fn属性
这里以1+2的token为例,这里会用到parse的expect方法,expect会用到peek方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | peek: function (e1, e2, e3, e4) { if ( this .tokens.length > 0) { var token = this .tokens[0]; var t = token.text; if (t === e1 || t === e2 || t === e3 || t === e4 || (!e1 && !e2 && !e3 && !e4)) { return token; } } return false ; }, expect: function (e1, e2, e3, e4){ var token = this .peek(e1, e2, e3, e4); if (token) { if ( this .json && !token.json) { this .throwError( ‘is not valid json‘ , token); } this .tokens.shift(); return token; } return false ; } |
expect方法传空就是默认从token数组中弹出第一个token,数组数量减1
1+2的执行语句最后会定位到加法运算那里additive
1 2 3 4 5 6 7 8 | additive: function () { var left = this .multiplicative(); var token; while ((token = this .expect( ‘+‘ , ‘-‘ ))) { left = this .binaryFn(left, token.fn, this .multiplicative()); } return left; } |
最后返回一个二元操作的函数binaryFn
1 2 3 4 5 6 7 | binaryFn: function (left, fn, right) { return extend( function (self, locals) { return fn(self, locals, left, right); }, { constant:left.constant && right.constant }); } |
这个函数参数里的left,right对应的‘1‘,‘2‘两个token的fn属性,即是
js function(){ return number;}
fn函数对应additive方法中+号对应token的fn
1 2 3 4 5 6 7 8 9 | 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;} |
最后生成执行表达式函数,也就是filterChain返回的left值,被push到statements方法中的statements数组中,仔细看statements方法的返回值,假如表达式数组长度为1,则返回第一个执行表达式,否则返回一个包装的函数,里面是一个loop,不断的执行表达式,只返回最后一个表达式的值
1 2 3 4 5 6 7 8 9 10 11 12 | return (statements.length === 1) ? statements[0] : function (self, locals) { var value; for ( var i = 0; i < statements.length; i++) { var statement = statements[i]; if (statement) { value = http://www.mamicode.com/statement(self, locals); } } return value; } |
好了,说完了生成执行表达式,其实parse的任务已经完成了,现在只需要把这个作为parseprovider的返回值了.
等会再回到rootscope的$watch函数解析里去,我们可以先测试下parse解析生成执行表达式的效果,这里贴一个独立的带parse的例子,不依赖angularjs,感兴趣的可以戳这里
总结
今天先说到这里了,下次有空接着分析rootscope后续的方法.
作者声明
作者: feenan
出处: http://www.cnblogs.com/xuwenmin888
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。