zoukankan      html  css  js  c++  java
  • 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服务,将讲解指令是如何实现的,梳理指令的整个执行流程。

  • 相关阅读:
    【mysql】sum处理null的结果
    【ELK】【docker】6.Elasticsearch 集群启动多节点 + 解决ES节点集群状态为yellow
    【ELK】5.spring boot日志集成ELK,搭建日志系统
    【MySQL】EXPLAIN命令详解--解释执行计划
    【java】ThreadLocal线程变量的实现原理和使用场景
    【工具类】根据IP获取IP归属地,国家,城市信息
    【POI】maven引用POI的依赖,XSSFWorkbook依旧无法使用的问题。
    Spring Boot 2实现分布式锁——这才是实现分布式锁的正确姿势!
    frp服务搭建
    RedisTemplate的key默认序列化器问题
  • 原文地址:https://www.cnblogs.com/web2-developer/p/angular-12.html
Copyright © 2011-2022 走看看