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


一、首先拋出兩個問題

問題一:在angular中我們綁定數據最基本的方式是用兩個大括號將$scope的變量包裹起來,那么如果想將大括號換成其他什么符號,比如換成[{}],可不可以呢,如果可以在哪里配置呢?
問題二:綁定的數據是如何被解析的呢?我們通過對$parse的分析,應該猜到綁定到模版的表達式最終會被傳給$parse服務來處理,那么是誰將表達式從html字符串中給讀取出來的呢?

二、$interpolate的功能

$interpolate是一個angular的內部服務,專門給$compile(等把$compile所依賴的服務講完,我們就會分析$compile的代碼了)調用的,而他的作業也比較簡單:就是重字符中綁定的數據給解析出來。其中,它本身只完成獲取數據表達式,表達式的解析將交給$parse服務來完成。
為什么要說$interpolate和$parse干的一樣是臟活累活呢?其實這里主要指的的累活,$interpolate將會被頻繁調用,對代碼的質量要求比較高。

三、源代碼

1.$InterpolateProvider提供修改綁定數據時用的插入標記(interpolation markup)的能力

  var startSymbol = '{{';
  var endSymbol = '}}';

  this.startSymbol = function(value) {
    if (value) {
      startSymbol = value;
      return this;
    } else {
      return startSymbol;
    }
  };

  this.endSymbol = function(value) {
    if (value) {
      endSymbol = value;
      return this;//如果設置成功,返回對象的引用,提供優雅的鏈式書寫能力
    } else {
      return endSymbol;
    }
  };

解決第一個問題,我們可以通過在模塊的配置代碼中寫入$InterpolateProvider.startSymbol('[{').endSymbol('}]')來將默認的插入標記改為[{}]

2.插入標記被用來表示了綁定數據的開始和結束,那么,我們怎么來表示他們本身呢。

請看下面的代碼,初次看的時候,這里的代碼有些難懂。

   var escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),//startSymbol.replace(/./g, escape)會給startSymbol插入三個反斜杠
        escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');

    function escape(ch) {
      return '\\\\\\' + ch;//因為轉義的原因,ch前面的字符將是三個反斜杠符號
    }

    function unescapeText(text) {
      return text.replace(escapedStartRegexp, startSymbol).
        replace(escapedEndRegexp, endSymbol);
    }

假設插入標記就是默認的{{}},escapedStartRegexp和escapedEndRegexp將會是什么?

3.從字符串中解出表達式

function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
      allOrNothing = !!allOrNothing;
      var startIndex,
          endIndex,
          index = 0,
          expressions = [],
          parseFns = [],
          textLength = text.length,
          exp,
          concat = [],
          expressionPositions = [];

      while (index < textLength) {
        if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
             ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
          if (index !== startIndex) {
            concat.push(unescapeText(text.substring(index, startIndex)));
          }
          exp = text.substring(startIndex + startSymbolLength, endIndex);
          expressions.push(exp);
          parseFns.push($parse(exp, parseStringifyInterceptor));
          index = endIndex + endSymbolLength;
          expressionPositions.push(concat.length);
          concat.push('');
        } else {
          // we did not find an interpolation, so we have to add the remainder to the separators array
          if (index !== textLength) {
            concat.push(unescapeText(text.substring(index)));
          }
          break;
        }
      }
...

從上面的代碼實現來看,作者還是沒有采用效率比較的低的正則表達式來完成表達式的識別。上面的代碼完成的功能是將字符串分組(壓入concat數組),分組的邊界時表達式插入符,表達式本身用空格占位,表達式本則壓入另一個數據(parseFns),並且記錄下表達式在concat的位置。這里看到表達式的解析依然是調用的$parse服務。

4.$interpolate最終返回的是什么?

      if (trustedContext && concat.length > 1) {
          $interpolateMinErr.throwNoconcat(text);
      }

      if (!mustHaveExpression || expressions.length) {
        var compute = function(values) { //計算表達式值,並且拼接為字符串
          for (var i = 0, ii = expressions.length; i < ii; i++) {
            if (allOrNothing && isUndefined(values[i])) return;
            concat[expressionPositions[i]] = values[i];
          }
          return concat.join('');
        };

        var getValue = function(value) { //獲取值
          return trustedContext ?
            $sce.getTrusted(trustedContext, value) : //利用$sce進行檢查
            $sce.valueOf(value);
        };

        return extend(function interpolationFn(context) {
            var i = 0;
            var ii = expressions.length;
            var values = new Array(ii);

            try {
              for (; i < ii; i++) {
                values[i] = parseFns[i](context);
              }

              return compute(values);
            } catch (err) {
              $exceptionHandler($interpolateMinErr.interr(text, err));
            }

          }, {
          // all of these properties are undocumented for now
          exp: text, //just for compatibility with regular watchers created via $watch
          expressions: expressions,
          $$watchDelegate: function(scope, listener) { //監聽的代理,通過Scope.$watchGroup對text中的所有表達式進行監聽
            var lastValue;
            return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
              var currValue = compute(values);
              if (isFunction(listener)) {
                listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
              }
              lastValue = currValue;
            });
          }
        });
      }

      function parseStringifyInterceptor(value) { 
        try {
          value = getValue(value);
          return allOrNothing && !isDefined(value) ? value : stringify(value);
        } catch (err) {
          $exceptionHandler($interpolateMinErr.interr(text, err));
        }
      }
    }

$interpolate將返回一個函數,這個函數能夠獲取到text被解析后的值。這個函數並且綁定了exp,expressions和$$watchDelegate三個屬性。

上一期:angular源碼分析:angular中入境檢察官$sce
下一期:angular源碼分析:$compile服務——directive他媽
ps:在下一期中,我們會講解$compile服務,將講解指令是如何實現的,梳理指令的整個執行流程。


免責聲明!

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



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