Angularjs源码分析:$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,'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三个属性

相关文章

AngularJS 是一个JavaScript 框架。它可通过 注:建议把脚本放在 元素的底部。这会提高网页加载速度,因...
angluarjs中页面初始化的时候会出现语法{{}}在页面中问题,也即是页面闪烁问题。出现这个的原因是:由于...
AngularJS 通过被称为指令的新属性来扩展 HTML。AngularJS 指令AngularJS 指令是扩展的 HTML 属性,带有...
AngularJS 使用表达式把数据绑定到 HTML。AngularJS 表达式AngularJS 表达式写在双大括号内:{{ expres...
ng-repeat 指令可以完美的显示表格。在表格中显示数据 {{ x.Name }} {{ x.Country }} 使用 CSS 样式为了...
$http是 AngularJS 中的一个核心服务,用于读取远程服务器的数据。读取 JSON 文件下是存储在web服务器上...