本文主要分析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
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
