angular源碼分析:angular中臟活累活承擔者之$parse


我們在上一期中講 $rootscope時,看到$rootscope是依賴$prase,其實不止是$rootscope,翻看angular的源碼隨便翻翻就可以發現很多地方是依賴於$parse的。而$parse的源碼打開一看,它的代碼量有接近兩千行。翻開angular的api文檔,官方只給出了簡短的解釋"Converts Angular expression into a function(將一個angular的表達式轉化為一個函數)",心中神獸奔騰————就這么點功能為什么要“兩千行代碼”呢。

一、分享一下源碼作者的第一段注釋


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *     Any commits to this file should be reviewed with security in mind.  *
 *   Changes to this file can potentially create security vulnerabilities. *
 *          An approval from 2 Core members with history of modifying      *
 *                         this file is required.                          *
 *                                                                         *
 *  Does the change somehow allow for arbitrary javascript to be executed? *
 *    Or allows for someone to change the prototype of built-in objects?   *
 *     Or gives undesired access to variables likes document or window?    *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

這段聲明,我只在angular源碼中的三個文件中看到:$parse,$sce,$compile。

這段聲明的大體意思是:所有提交到這個文件的修改都會因為出於安全性的考慮被檢視。修改這個文件可能會帶來安全漏洞。至少需要兩位angular的核心成員審批,這個文件才能修改。所做的修改是否會導致任意的js代碼的執行,或者允許某人修改綁定在對象上的原型(prototype),或者給予不被希望的訪問變量(比如document,window)的權限。

敢情,作者實現了一個編譯器,正在學習編譯原理的同學有福了
通過閱讀后面的注釋以及https://docs.angularjs.org/guide/security,可以知道:這里實現$parse使用了2000行代碼是出於安全性的考慮,而使用了編譯器的方式來編譯需要解析的表達式。在詞法解析的過程中,本來可以使用正則表達式的,但是作者使用了自己構建一個詞法解析器(Lexer),我的猜測是出於性能的考慮。通過編譯原理的學習,我們知道詞法的解析只需要3型文法,而3型文法等價於正則表示,所以用正則表示能夠識別所有的需要的詞法。利用通行的正則表達式,固然可以很快的寫出一個詞法分析器,但是效率卻是很低的,正則表達式的執行,需要反復多次的遍歷字符串,對於被頻繁調用的$parse服務(我們說它干的是臟活累活)用正則表達式來實現詞法分析器顯然是不合適的。

二、編譯原理(這一部分對於angular的源碼分析,沒有太大卵用,可以跳過)

對於編譯原理,其實就是計算機科學家在編寫編譯器過程中總結出來的一套理論。而編譯器,是一個程序,它的輸入是一種編程語言,輸出結果是另外一種編程語言,相當於是一個翻譯官,比如可以將英語翻譯成中文。抽象出來,就是將A語言翻譯成B語言的程序,稱之為編譯器。這會帶來一個問題,A語言和B語言的描述能力是否相同,就是A語言和B語言是否等價,是否能否翻譯。

說點題外話
讀者有沒有想過這個問題:為什么人類的語言能夠相互翻譯呢?反正,這個問題我是一直在思索,最近在讀《喬姆斯基的思想與理想》算是找到了一種解釋:人類有一種共同內在語言,喬姆斯基稱之為I語言,這是源自於人類的固有屬性的東西,而我們使用的漢語、英語神馬的都不過是一種表觀的語言,它們是I語言的編碼,其描述能力自然等價於I語言,那么這些語言的表述能力也就相等了。(喬姆斯基是現在還健在的大師,我個人覺得可以將他與牛頓、達爾文、愛因斯坦等人齊名)

1.編譯器的一般構成:

一個編譯器一般包含這幾個子程序:代碼預處理程序,詞法分析器,語法分析器,語義分析器,目標代碼生成器,目標代碼優化器等。
代碼預處理程序:作用在於干掉代碼中的不相關的部分,包括注釋、多余的空格,以及將預編譯指令翻譯成代碼。

詞法分析器:根據語言規定的詞匯表規則識別出合法的詞匯,如果有非法詞匯停止編譯,拋出錯誤信息。比如,在寫C語言中,對標識符的定義是,以字母或下划線開頭可以包含字母、數字、下划線的字符串,詞法分析器就是根據這個定義來識別標識符。
算了,限於篇幅,后面省略。。。。

2.文法分類

1956年,Chomsky建立形式語言的描述。通過對產生式的施加不同的限制,Chomsky把文法分為4種類型
首先定義一個產生式α→β

0型文法(PSG):

α∈(VN∪VT)* ,且至少含一個VN

β∈(VN∪VT)*
對產生式沒有任何限制
例如:A0→A0,A1→B
0型文法說明:
0型文法也稱為短語文法。

一個非常重要的理論結果是,0型文法的能力相當於圖靈機(Turing),關於圖靈機,我的另一篇博文《第一章,計算機的發明》有講到。或者說,任何0型語言都是遞歸可枚舉的;反之,遞歸可枚舉集必定是一個0型語言。對0型文法產生式的形式作某些限制,以給出1,2和3型文法的定義。
注意
文法G 定義為四元組(VN ,VT ,P,S)
VN :非終結符集
VT :終結符集
P :產生式集合(規則集合)
S :開始符號(識別符號)

1型文法(上下文有關文法context-sensitive):

對任一產生式α→β,都有|β|>=|α|, 僅僅S→ε除外
  產生式的形式描述:α1Aα2→α1βα2
  (其中,α1、α2、β∈(VN∪VT)*,β≠ε,A∈VN)
  即:A只有出現在α1α2的上下文中,才允許用β替換。
  產生的語言稱“上下文有關語言”
  例如:0A0→011000,1A1→101011

2型文法(CFG):

對任一產生式α→β,都有α∈VNβ∈(VN∪VT)*
  產生式的形式描述:A→β(A∈VN)
  即β取代A時,與A所處的上下文無關。
  產生的語言稱“上下文無關語言”
  例如:G[S]:S→01 S→0S1

3型文法(RG):也稱正規文法

每個產生式均為A→aBA→a —— 右線性
  A→BaA→a —— 左線性
  其中,A、B∈VN,a∈VT*
  產生的語言稱“正規語言”
  例如:G[S]: S→0A | 0,A→1B | B,B→1 | 0

三、$parse的詞法解析器

1.在講源代碼前,先講字符串搜索的預搜索技術.

在匹配運算符號的時候會出現一個問題:比如,在匹配x += y中的運算符時,如果逐個字符掃描,將得到連接x和y中的中間運算符是+=,而我們知道這里其實是一個運算符號+=,那么怎么辦呢?
很簡單:遇到+時,我們先不要斷言它是一個"加預算符",繼續讀入一個字符,看這兩個字符是否能夠組成一個新的符號,如果能組成則一個識別,索引標簽步進兩個單位,如果不能則判斷+號是"加運算符",提前讀入的字符不算數。
我們將這種預先處理的技術,稱為"預搜索技術"。
推廣下去,你會得到KMP算法

2.詞法解析器的源代碼:

var Lexer = function(options) {
  this.options = options;
};

Lexer.prototype = {
  constructor: Lexer,

  lex: function(text) { //將一個字符串轉換成一個詞匯表
    this.text = text;
    this.index = 0;
    this.tokens = [];

    while (this.index < this.text.length) {
      var ch = this.text.charAt(this.index);
      if (ch === '"' || ch === "'") { //識別字符串
        this.readString(ch);
      } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {//識別number(包括,整數、浮點數和科學計數法
        this.readNumber();
      } else if (this.isIdent(ch)) {//識別標識符
        this.readIdent();
      } else if (this.is(ch, '(){}[].,;:?')) { //識別邊界符號
        this.tokens.push({index: this.index, text: ch});
        this.index++;
      } else if (this.isWhitespace(ch)) { //識別空格
        this.index++;
      } else { //運算符的識別,一個運算符由1到3個字符組成,需要預搜索
        var ch2 = ch + this.peek();//預搜索1個字符
        var ch3 = ch2 + this.peek(2);//預搜索2個字符
        var op1 = OPERATORS[ch];
        var op2 = OPERATORS[ch2];
        var op3 = OPERATORS[ch3];
        if (op1 || op2 || op3) {
          var token = op3 ? ch3 : (op2 ? ch2 : ch);
          this.tokens.push({index: this.index, text: token, operator: true});
          this.index += token.length;
        } else {
          this.throwError('Unexpected next character ', this.index, this.index + 1); //發生異常,拋出
        }
      }
    }
    return this.tokens;//返回存儲下來的詞匯表
  },

  is: function(ch, chars) { //判斷ch是否是chars字符串中一個字符
    return chars.indexOf(ch) !== -1;
  },

  peek: function(i) { //返回,預讀i個字符
    var num = i || 1;
    return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
  },

  isNumber: function(ch) { //判斷字符是否是數字
    return ('0' <= ch && ch <= '9') && typeof ch === "string";
  },

  isWhitespace: function(ch) { //判斷字符是否是非打印字符
    // IE treats non-breaking space as \u00A0
    return (ch === ' ' || ch === '\r' || ch === '\t' ||
            ch === '\n' || ch === '\v' || ch === '\u00A0');
  },

  isIdent: function(ch) { //判斷ch是否是屬於標識符的字符集
    return ('a' <= ch && ch <= 'z' ||
            'A' <= ch && ch <= 'Z' ||
            '_' === ch || ch === '$');
  },

  isExpOperator: function(ch) { //是否科學技術法中的符號(+或者-)
    return (ch === '-' || ch === '+' || this.isNumber(ch));
  },

  throwError: function(error, start, end) { //錯誤拋出
    end = end || this.index;
    var colStr = (isDefined(start)
            ? 's ' + start +  '-' + this.index + ' [' + this.text.substring(start, end) + ']'
            : ' ' + end);
    throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
        error, colStr, this.text);
  },

  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++;
    }
    this.tokens.push({
      index: start,
      text: number,
      constant: true,
      value: Number(number)
    });
  },

  readIdent: function() { //讀入一個標識符
    var start = this.index;
    while (this.index < this.text.length) {
      var ch = this.text.charAt(this.index);
      if (!(this.isIdent(ch) || this.isNumber(ch))) {
        break;
      }
      this.index++;
    }
    this.tokens.push({
      index: start,
      text: this.text.slice(start, this.index),
      identifier: true
    });
  },

  readString: function(quote) { //讀入一個字符串
    var start = this.index;
    this.index++;
    var string = '';
    var rawString = quote;
    var escape = false;
    while (this.index < this.text.length) {
      var ch = this.text.charAt(this.index);
      rawString += ch;
      if (escape) {
        if (ch === 'u') {
          var hex = this.text.substring(this.index + 1, this.index + 5);
          if (!hex.match(/[\da-f]{4}/i)) {
            this.throwError('Invalid unicode escape [\\u' + hex + ']');
          }
          this.index += 4;
          string += String.fromCharCode(parseInt(hex, 16));
        } else {
          var rep = ESCAPE[ch];
          string = string + (rep || ch);
        }
        escape = false;
      } else if (ch === '\\') {
        escape = true;
      } else if (ch === quote) {
        this.index++;
        this.tokens.push({
          index: start,
          text: rawString,
          constant: true,
          value: string
        });
        return;
      } else {
        string += ch;
      }
      this.index++;
    }
    this.throwError('Unterminated quote', start);
  }
};

3.上面代碼對應的有限狀態機

有限狀態機

4.如果用正則表達式來實現

1.字符串:/(["'])\S+\1/
2.number:/([0-9]*.)?[0-9]+(e[\+-][0-9]+)?/
3.標識符:/[a-zA-Z_\$][a-zA-Z0-9_\$]*/
4.運算符:/\+|-|\*|\/|%|===|\!==|==|\!=|<|>|<=|>=|&&|\|\||\!|=|\|/

四、$parse的語法解析器

1.概念

AST:Abstract Syntax Tree,抽象語法樹
抽象語法樹(Abstract Syntax Tree ,AST)作為程序的一種中間表示形式,在程序分析等諸多領域有廣泛的應用。利用抽象語法樹可以方便地實現多種源程序處理工具,比如源程序瀏覽器、智能編輯器、語言翻譯器等。

2.$parse的AST實現:

var AST = function(lexer, options) {
  this.lexer = lexer;
  this.options = options;
};

//下面定義了語法樹節點的類型
AST.Program = 'Program';//代表程序的根節點
AST.ExpressionStatement = 'ExpressionStatement'; //表達式節點
AST.AssignmentExpression = 'AssignmentExpression';//賦值表達式
AST.ConditionalExpression = 'ConditionalExpression';//判斷表達式
AST.LogicalExpression = 'LogicalExpression';//邏輯運算表達式
AST.BinaryExpression = 'BinaryExpression';//二進制預算處理表達式(按位與、按位或)? 看了后面,才知道這種理解是不對的.這表示的大小關系.但是為什么要取這個名字呢?
AST.UnaryExpression = 'UnaryExpression';  //非數據表達式
AST.CallExpression = 'CallExpression'; //調用表達式
AST.MemberExpression = 'MemberExpression'; //成員變量表達式
AST.Identifier = 'Identifier'; //標識符
AST.Literal = 'Literal'; //文字,額,這里是指ture,false,null,undefined等
AST.ArrayExpression = 'ArrayExpression';//數據表達式
AST.Property = 'Property'; //對象屬性表達式
AST.ObjectExpression = 'ObjectExpression';//對象表達式
AST.ThisExpression = 'ThisExpression';//this表達式

// Internal use only
AST.NGValueParameter = 'NGValueParameter';

AST.prototype = {
  ast: function(text) {//語法樹的入口
    this.text = text;
    this.tokens = this.lexer.lex(text);//調用詞法分析器,得到合法的詞匯表

    var value = this.program();//調用"program"子程序,開始生成語法樹

    if (this.tokens.length !== 0) {
      this.throwError('is an unexpected token', this.tokens[0]);
    }

    return value;
  },

  program: function() {
    var body = [];
    while (true) {//循環,調用expressionStatement讀取語句
      if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
        body.push(this.expressionStatement());
      if (!this.expect(';')) {
        return { type: AST.Program, body: body};//返回一個語法樹
      }
    }
  },

  expressionStatement: function() { //表達式節點,代表的是js的一個語句
    return { type: AST.ExpressionStatement, expression: this.filterChain() };//其中調用過濾器鏈filterChain.這是是為識別過濾器的語法如:timestamp|date:'yyyy-mm-dd'
  },

  filterChain: function() { 
    var left = this.expression();//調用expression,獲取語法樹的節點
    var token;
    while ((token = this.expect('|'))) { //過濾器可能是以鏈狀存在的,比如:xxx|filter1|filter2...,所以需要循環處理
      left = this.filter(left); //調用filter
    }
    return left;
  },

  expression: function() {
    return this.assignment();
  },

  assignment: function() {//識別賦值語句
    var result = this.ternary();
    if (this.expect('=')) {
      result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='};
    }
    return result;
  },

  ternary: function() {//識別三元運算:xxx?Axxx:Bxxx;
    var test = this.logicalOR();
    var alternate;
    var consequent;
    if (this.expect('?')) {
      alternate = this.expression();
      if (this.consume(':')) {
        consequent = this.expression();
        return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent};
      }
    }
    return test;
  },

  logicalOR: function() { //識別邏輯運算 "或"(||)
    var left = this.logicalAND();
    while (this.expect('||')) {
      left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() };
    }
    return left;
  },

  logicalAND: function() { //識別邏輯運算 "與"(&&)
    var left = this.equality();
    while (this.expect('&&')) {
      left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()};
    }
    return left;
  },

  equality: function() { //識別邏輯運算 "等"(==)
    var left = this.relational();
    var token;
    while ((token = this.expect('==','!=','===','!=='))) {
      left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() };
    }
    return left;
  },

  relational: function() { //識別大小比較的表達式
    var left = this.additive();
    var token;
    while ((token = this.expect('<', '>', '<=', '>='))) {
      left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() };//沒搞懂為什么要取這個名字Binary,一開始還以為是二進制運算相關
    }
    return left;
  },

  additive: function() { //識別加減表達式
    var left = this.multiplicative();
    var token;
    while ((token = this.expect('+','-'))) {
      left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() };
    }
    return left;
  },

  multiplicative: function() {//識別乘除法
    var left = this.unary();
    var token;
    while ((token = this.expect('*','/','%'))) {
      left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() };
    }
    return left;
  },

  unary: function() { //處理非數組的表達式
    var token;
    if ((token = this.expect('+', '-', '!'))) {
      return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() };
    } else {
      return this.primary();
    }
  },

  primary: function() {//處理js的遠程語法寫的內容
    var primary;
    if (this.expect('(')) {
      primary = this.filterChain();
      this.consume(')');
    } else if (this.expect('[')) {
      primary = this.arrayDeclaration();
    } else if (this.expect('{')) {
      primary = this.object();
    } else if (this.constants.hasOwnProperty(this.peek().text)) {
      primary = copy(this.constants[this.consume().text]);
    } else if (this.peek().identifier) {
      primary = this.identifier();
    } else if (this.peek().constant) {
      primary = this.constant();
    } else {
      this.throwError('not a primary expression', this.peek());
    }

    var next;
    while ((next = this.expect('(', '[', '.'))) {
      if (next.text === '(') {
        primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() };
        this.consume(')');
      } else if (next.text === '[') {
        primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true };
        this.consume(']');
      } else if (next.text === '.') {
        primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false };
      } else {
        this.throwError('IMPOSSIBLE');
      }
    }
    return primary;
  },

  filter: function(baseExpression) {//處理過濾器語法
    var args = [baseExpression];//將傳入參數放入args ,做第一個參數節點
    var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true};//使用過濾器,其實質是調用一個函數,所以語法樹節點是一個"CallExpression"

    while (this.expect(':')) {
      args.push(this.expression());//壓縮其他參數節點
    }

    return result;
  },

  parseArguments: function() { //處理參數
    var args = [];
    if (this.peekToken().text !== ')') {
      do {
        args.push(this.expression());
      } while (this.expect(','));
    }
    return args;
  },

  identifier: function() { //處理標識符
    var token = this.consume();
    if (!token.identifier) {
      this.throwError('is not a valid identifier', token);
    }
    return { type: AST.Identifier, name: token.text };
  },

  constant: function() { //處理常量
    // TODO check that it is a constant
    return { type: AST.Literal, value: this.consume().value };
  },

  arrayDeclaration: function() { //處理數組
    var elements = [];
    if (this.peekToken().text !== ']') {
      do {
        if (this.peek(']')) {
          // Support trailing commas per ES5.1.
          break;
        }
        elements.push(this.expression());
      } while (this.expect(','));
    }
    this.consume(']');

    return { type: AST.ArrayExpression, elements: elements };
  },

  object: function() {//處理Object
    var properties = [], property;
    if (this.peekToken().text !== '}') {
      do {
        if (this.peek('}')) {
          // Support trailing commas per ES5.1.
          break;
        }
        property = {type: AST.Property, kind: 'init'};
        if (this.peek().constant) {
          property.key = this.constant();
        } else if (this.peek().identifier) {
          property.key = this.identifier();
        } else {
          this.throwError("invalid key", this.peek());
        }
        this.consume(':');
        property.value = this.expression();
        properties.push(property);
      } while (this.expect(','));
    }
    this.consume('}');

    return {type: AST.ObjectExpression, properties: properties };
  },

  throwError: function(msg, token) {
    throw $parseMinErr('syntax',
        'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
          token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
  },

  consume: function(e1) {
    if (this.tokens.length === 0) {
      throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
    }

    var token = this.expect(e1);
    if (!token) {
      this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
    }
    return token;
  },

  peekToken: function() {
    if (this.tokens.length === 0) {
      throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
    }
    return this.tokens[0];
  },

  peek: function(e1, e2, e3, e4) {
    return this.peekAhead(0, e1, e2, e3, e4);
  },

  peekAhead: function(i, e1, e2, e3, e4) {
    if (this.tokens.length > i) {
      var token = this.tokens[i];
      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) {//期望子程序,如果滿足期望,將this.tokens隊列頭部彈出一個元素返回,否則返回false
    var token = this.peek(e1, e2, e3, e4);
    if (token) {
      this.tokens.shift();
      return token;
    }
    return false;
  },


  /* `undefined` is not a constant, it is an identifier,
   * but using it as an identifier is not supported
   */
  constants: {
    'true': { type: AST.Literal, value: true },
    'false': { type: AST.Literal, value: false },
    'null': { type: AST.Literal, value: null },
    'undefined': {type: AST.Literal, value: undefined },
    'this': {type: AST.ThisExpression }
  }
};

3.語法樹的數據結構:

從上面的代碼中可以得知,語法樹的一個節點的數據結構

{ 
   type: AST.xxxStatement, //節點類型
   xxx:xxx, //每種節點類型的所含的元素不同   
}

//1.語法樹根節點
{   
    type: AST.Program,    
    body: body
}
//2.語法數表達式節點
{   
    type: AST.ExpressionStatement,    
    expression: this.filterChain() 
}
//3.賦值語句節點
{   
    type: AST.AssignmentExpression,   
    left: result,     
    right: this.assignment(),   
    operator: '='
}
//4.條件表達式節點(三目預算)
{   
    type: AST.ConditionalExpression,  
    test: test,  
    alternate: alternate,    
    consequent: consequent
}
//5.邏輯預算節點
{   
    type: AST.LogicalExpression,  
    operator: '||',  
    left: left,  
    right: this.logicalAND() 
}
//6.比較運算節點
{   
    type: AST.BinaryExpression,   
    operator: token.text,     
    left: left,     
    right: this.relational() 
}
//7.非數據節點
{   
    type: AST.UnaryExpression,    
    operator: token.text,  
    prefix: true,    
    argument: this.unary() 
}
//8.成員變量節點
{   
    type: AST.MemberExpression,   
    object: primary,  
    property: this.expression(),     
    computed: true 
}
//9.標識符節點
{   
    type: AST.Identifier,     
    name: token.text 
}
//10.常量節點
{   
    type: AST.Literal,    
    value: this.consume().value 
}
//11.數據節點
{   
    type: AST.ArrayExpression,    
    elements: elements 
}

4.舉例

假設angular表達式是str = ok.str;str.length>2?'abc':123|test:look;
那么通過上面的程序,得到的語法樹將是:

語法樹

五、$parse編譯器

在$parse中定義了兩個編譯器:ASTCompiler 和 ASTInterpreter,他們的作用都是調用AST生成語法樹,然后將語法樹組裝成js函數。
兩者之間的差別是
1.ASTCompiler 會將句法樹按語義組裝成函數,ASTInterpreter是按語義組裝成語句,然后調用循環,主句執行;
2.ASTInterpreter的語句執行過程中,會被附加ensureSafeXXX的函數,進行安全性檢查。
3.看看,他們都是怎么被調用的:

/**
 * @constructor
 */
var Parser = function(lexer, $filter, options) {
  this.lexer = lexer;
  this.$filter = $filter;
  this.options = options;
  this.ast = new AST(this.lexer);
  this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : //什么是csp??
                                   new ASTCompiler(this.ast, $filter);
};

Parser.prototype = {
  constructor: Parser,

  parse: function(text) {
    return this.astCompiler.compile(text, this.options.expensiveChecks);
  }
};

什么是csp,請看CSP 1.1 specification;
還有一篇:瀏覽器安全策略說之內容安全策略CSP;
另外推薦angular-ngcspangular-Security
寫得太多了,其他代碼就不再分析了。如果有機會,后面再補一篇博文。

六、$parse服務

這里,將$parse代碼函數代碼貼出來,如果上面的代碼明白了,下面的也就很簡單了,不再贅述了.

function $parse(exp, interceptorFn, expensiveChecks) {
      var parsedExpression, oneTime, cacheKey;

      switch (typeof exp) {
        case 'string':
          exp = exp.trim();
          cacheKey = exp;

          var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
          parsedExpression = cache[cacheKey];

          if (!parsedExpression) {
            if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
              oneTime = true;
              exp = exp.substring(2);
            }
            var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
            var lexer = new Lexer(parseOptions);
            var parser = new Parser(lexer, $filter, parseOptions);
            parsedExpression = parser.parse(exp);
            if (parsedExpression.constant) {
              parsedExpression.$$watchDelegate = constantWatchDelegate;
            } else if (oneTime) {
              parsedExpression.$$watchDelegate = parsedExpression.literal ?
                  oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
            } else if (parsedExpression.inputs) {
              parsedExpression.$$watchDelegate = inputsWatchDelegate;
            }
            cache[cacheKey] = parsedExpression;
          }
          return addInterceptor(parsedExpression, interceptorFn);

        case 'function':
          return addInterceptor(exp, interceptorFn);

        default:
          return noop;
      }
    };

這篇博文,我從昨天寫到今天,終於勉強寫完了。
你說$parse是不是專干臟活和累活呢?
上一期:angular源碼分析:angular中$rootscope的實現——scope的一生
下一期:angular源碼分析:angular源碼分析:angular中入境檢察官$sce
ps:sec,是Strict Contextual Escaping的縮寫。這個可以被我們用的較少,這里給api的鏈接:$sec


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM