本文主要分析RootScopeProvider和ParseProvider
RootScopeProvider簡介
今天這個rootscope可是angularjs里面比較活躍的一個provider,大家可以理解為一個模型M或者VM,它主要負責與控制器或者指令進行數據交互.
今天使用的源碼跟上次分析的一樣也是1.2.X系列,只不過這次用的是未壓縮合並版的,方便大家閱讀,可以在這里下載
從$get屬性說起
說起這個$get屬性,是每個系統provider都有的,主要是先保存要實例化的函數體,等待instanceinjector.invoke的時候來調用,因為$get的代碼比較多,所以先上要講的那部分,大家可以注意到了,在$get上面有一個digestTtl方法
this.digestTtl = function(value) {
if (arguments.length) {
TTL = value;
}
return TTL;
};
這個是用來修改系統默認的dirty check次數的,默認是10次,通過在config里引用rootscopeprovider,可以調用這個方法傳遞不同的值來修改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, 暫無
通過這段代碼,可以看出,系統默認會創建根作用域,並作為$rootScopeprovider實例返回.
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() {
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函數,這個比較關鍵
$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函數有三個參數,第一個是監控參數,可以是字符串或者函數,第二個是監聽函數,第三個是代表是否深度監聽,注意看這個代碼
get = compileToFn(watchExp, 'watch')
這個compileToFn函數其實是調用$parse實例來分析監控參數,然后返回一個函數,這個會在dirty check里用到,用來獲取監控表達式的值,這個$parseprovider也是angularjs中用的比較多的,下面來重點的說下這個provider
$parse的代碼比較長,在源碼文件夾中的ng目錄里,parse.js里就是$parse的全部代碼,當你了解完parse的核心之后,這部份代碼其實可以獨立出來,做成自己的計算器程序也是可以的,因為它的核心就是解析字符串,而且默認支持四則運算,運算符號的優先級處理,只是額外的增加了對變量的支持以及過濾器的支持,想想,把這塊代碼放在模板引擎里也是可以的,說多了,讓我們來一步一步的分析parse代碼吧.
記住,不管是哪個provider,先看它的$get屬性,所以我們先來看看$parse的$get吧
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,生成執行表達式,其實就是返回一個執行函數
看這里
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里去
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 = 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方法
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方法
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,它保存着所有運算符所對應的動作,比如這里的+,對應的動作是
'+':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來處理,來看看
var value = json ? this.primary() : this.statements();
默認json是false,所以會跳到this.statements(),這里將會生成執行語句.
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 = 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方法,expect會用到peek方法
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
additive: function() {
var left = this.multiplicative();
var token;
while ((token = this.expect('+','-'))) {
left = this.binaryFn(left, token.fn, this.multiplicative());
}
return left;
}
最后返回一個二元操作的函數binaryFn
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
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,不斷的執行表達式,只返回最后一個表達式的值
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 = statement(self, locals);
}
}
return value;
}
好了,說完了生成執行表達式,其實parse的任務已經完成了,現在只需要把這個作為parseprovider的返回值了.
等會再回到rootscope的$watch函數解析里去,我們可以先測試下parse解析生成執行表達式的效果,這里貼一個獨立的帶parse的例子,不依賴angularjs,感興趣的可以戳這里
總結
今天先說到這里了,下次有空接着分析rootscope后續的方法.
作者聲明
作者: feenan
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
