angluar浅析3 $CompileProvider

angular的编译和渲染主要通过$CompileProvider这个服务来实现的
一个简单的示例如下

 <div ng-app="myApp" ng-controller="myCtrl">
        {{msg}}
        <runoob-directive></runoob-directive>
    </div>

 var app = angular.module('myApp', []);
app.directive("runoobDirective", function() {
    return {
        template : "<h1>自定义指令!</h1>"
    };
});

app.controller('myCtrl', function($scope) {
    $scope.msg = "hello word";
});

在该示例中compile调用的结构图如下所示


compile1.png

有图可知compile1包括两个阶段

  1. compileNodes阶段, 该过程主要是解析元素各种指令进行指令的收集,然后生成link函数。
    上述示例中可将{{{msg}}}解析为{compile: textInterpolateLinkFn},然后push到当前元素的指令集中。
  2. compositeLinkFn阶段, 该过程主要执行上一阶段生成的link函数, 包括preLinkFns、postLinkFns的调用。
    {{{msg}}}生成的link函数会放入到postLinkFns中,用于页面渲染。

CompileProvider

该方法通过directive方法实现指令的注册, 返回compile作为后续编译渲染使用。hasDirectives用来缓存指令集工厂方法。

function $CompileProvider($provide, $$sanitizeUriProvider) {
  var hasDirectives = {},
    ...
   this.directive = function registerDirective(name, directiveFactory) {
       if (!hasDirectives.hasOwnProperty(name)) {
        hasDirectives[name] = [];
        $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
          function($injector, $exceptionHandler) {
            var directives = [];
            forEach(hasDirectives[name], function(directiveFactory, index) {
              try {
                var directive = $injector.invoke(directiveFactory);
                if (isFunction(directive)) {
                  directive = { compile: valueFn(directive) };
                } else if (!directive.compile && directive.link) {
                  directive.compile = valueFn(directive.link);
                }
                directive.priority = directive.priority || 0;
                directive.index = index;
                directive.name = directive.name || name;
                directive.require = directive.require || (directive.controller && directive.name);
                directive.restrict = directive.restrict || 'EA';
                if (isObject(directive.scope)) {
                  directive.$$isolateBindings = parseIsolateBindings(directive.scope, directive.name);
                }
                directives.push(directive);
              } catch (e) {
                $exceptionHandler(e);
              }
            });
            return directives;
          }]);
      }
      hasDirectives[name].push(directiveFactory);
    } else {
      forEach(name, reverseParams(registerDirective));
    }
    return this;
  };


  this.$get = [
            '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
            '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
    function($injector,   $interpolate,   $exceptionHandler,   $templateRequest,   $parse,
             $controller,   $rootScope,   $document,   $sce,   $animate,   $$sanitizeUri) {

    return compile;
             } 
 }              

compile

compile作用上述已做说明

function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,
                        previousCompileContext) {
      var compositeLinkFn =
              compileNodes($compileNodes, transcludeFn, $compileNodes,
                           maxPriority, ignoreDirective, previousCompileContext);
      ....                     
      return function publicLinkFn(scope, cloneConnectFn, options) {
        $linkNode = $compileNodes;
        if (transcludeControllers) {
          for (var controllerName in transcludeControllers) {
            $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
          }
        }

        $linkNode = $compileNodes;
        compile.$$addScopeInfo($linkNode, scope);

        if (cloneConnectFn) cloneConnectFn($linkNode, scope);
        if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
        return $linkNode;
      };
    }

compileNodes

  1. 进行指令的收集工作
  2. 将指令作用到元素上
  3. 因为元素节点包括当前节点和子节点, 在生成link函数的过程中需要对这两种情况进行处理。
    该方法在内部定义compositeLinkFn用来将当前link和其后代的link进行组合, 定义一个数组linkFns用来存储生成的link函数,利用闭包的特性实现递归处理。
function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,
                            previousCompileContext) {
      var linkFns = [],
          attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;

      for (var i = 0; i < nodeList.length; i++) {
        attrs = new Attributes();

        // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
        directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined,
                                        ignoreDirective);

        nodeLinkFn =  applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement,
                                      null, [], [], previousCompileContext)

        ...

        // 注意此处,对于children采用递归处理
        childLinkFn =  compileNodes(childNodes,
                 nodeLinkFn ? (
                  (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
                     && nodeLinkFn.transclude) : transcludeFn);
        inkFns.push(i, nodeLinkFn, childLinkFn);             

        //use the previous context only for the first element in the virtual group
        previousCompileContext = null;
      }

      // return a linking function if we have found anything, null otherwise
      return linkFnFound ? compositeLinkFn : null;

      function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
        var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn;
        var stableNodeList;

        for (i = 0, ii = linkFns.length; i < ii;) {
          node = stableNodeList[linkFns[i++]];
          nodeLinkFn = linkFns[i++];
          childLinkFn = linkFns[i++];

          if (nodeLinkFn) {
            nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);

          } else if (childLinkFn) {
            childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
          }
        }
      }
    }

通过下图简单的说明link的包装结构


linkbaozhuang.JPG

collectDirectives

进行指令收集

function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
      var nodeType = node.nodeType,


      switch (nodeType) {
        case NODE_TYPE_ELEMENT: /* Element */
          // use the node name: <directive>
          addDirective(directives,
              directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
        ...
      }

      directives.sort(byPriority);
      return directives;
    }

applyDirectivesToNode

有代码可知带有模板的指令会在此处生成node的节点并挂载到当前节点上(通过指令来定义一个组件时需要有模板或者模板编译函数)。
执行directive.compile方法生成linkFn
a. 如果linkFn的值是个方法则将其推入到 postLinkFns 中
b. 如果linkFn的值是一个对象,则分别将pre, post推入到preLinkFns、postLinkFns中。

function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
                                   jqCollection, originalReplaceDirective, preLinkFns, postLinkFns,
                                   previousCompileContext) {
      ...

      // executes all directives on the current element
      for (var i = 0, ii = directives.length; i < ii; i++) {
        directive = directives[I];

        ...
        if (directive.template) {
          hasTemplate = true;
          assertNoDuplicate('template', templateDirective, directive, $compileNode);
          templateDirective = directive;

          directiveValue = (isFunction(directive.template))
              ? directive.template($compileNode, templateAttrs)
              : directive.template;

          directiveValue = denormalizeTemplate(directiveValue);

          if (directive.replace) {
            ...
          } else {
            $compileNode.html(directiveValue);
          }
        }

        if (directive.templateUrl) {
          ...
        } else if (directive.compile) {
          try {
            linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
            if (isFunction(linkFn)) {
              addLinkFns(null, linkFn, attrStart, attrEnd);
            } else if (linkFn) {
              addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
            }
          } catch (e) {
            $exceptionHandler(e, startingTag($compileNode));
          }
        }
        ...

      }


      nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
      nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
      nodeLinkFn.elementTranscludeOnThisElement = hasElementTranscludeDirective;
      nodeLinkFn.templateOnThisElement = hasTemplate;
      nodeLinkFn.transclude = childTranscludeFn;

      previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;

      // might be normal or delayed nodeLinkFn depending on if templateUrl is present
      return nodeLinkFn;
      function nodeLinkFn(...) {

      }
    }   

nodeLinkFn

该方法的主要功能, 执行controller方法、调用preLinkFns、递归调用子节点编译函数、postLinkFns

function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
        var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element,
            attrs;

        if (compileNode === linkNode) {
          attrs = templateAttrs;
          $element = templateAttrs.$$element;
        } else {
          $element = jqLite(linkNode);
          attrs = new Attributes($element, templateAttrs);
        }

        if (newIsolateScopeDirective) {
          isolateScope = scope.$new(true);
        }

        if (boundTranscludeFn) {
          // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
          // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
          transcludeFn = controllersBoundTransclude;
          transcludeFn.$$boundTransclude = boundTranscludeFn;
        }

        if (controllerDirectives) {
          // TODO: merge `controllers` and `elementControllers` into single object.
          controllers = {};
          elementControllers = {};
          forEach(controllerDirectives, function(directive) {
           
            controllerInstance = $controller(controller, locals, true, directive.controllerAs);
            elementControllers[directive.name] = controllerInstance;

            controllers[directive.name] = controllerInstance;
          });
        }

        
        if (controllers) {
          forEach(controllers, function(controller) {
            controller();
          });
          controllers = null;
        }

        // PRELINKING
        for (i = 0, ii = preLinkFns.length; i < ii; i++) {
          linkFn = preLinkFns[I];
          invokeLinkFn(linkFn,
              linkFn.isolateScope ? isolateScope : scope,
              $element,
              attrs,
              linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
              transcludeFn
          );
        }

        // RECURSION
        // We only pass the isolate scope, if the isolate directive has a template,
        // otherwise the child elements do not belong to the isolate directive.
        var scopeToChild = scope;
        if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
          scopeToChild = isolateScope;
        }
        childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);

        // POSTLINKING
        for (i = postLinkFns.length - 1; i >= 0; i--) {
          linkFn = postLinkFns[I];
          invokeLinkFn(linkFn,
              linkFn.isolateScope ? isolateScope : scope,
              $element,
              attrs,
              linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
              transcludeFn
          );
        }

        function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
            try {
                linkFn(scope, $element, attrs, controllers, transcludeFn);
            } catch (e) {
                $exceptionHandler(e, startingTag($element));
            }
        }
        ...
      }
    }

补充
@ 设置属性的单项绑定、 =双向绑定 &事件的绑定
查看双向绑定可知, 设置lastValue属性来保存watch监听之前的父元素的值。 在watcher过程中会进行父子元素比较如果不同则说明有一个值发生变化。 如果此时的父元素与lastValue相同则说明子元素发生了变化,需要通知父元素进行变更;否则说明父元素发生变化,需重新设置子元素。

switch (mode) {

case '@':
    attrs.$observe(attrName, function(value) {
    isolateBindingContext[scopeName] = value;
    });
    attrs.$$observers[attrName].$$scope = scope;
    if (attrs[attrName]) {
    // If the attribute has been provided then we trigger an interpolation to ensure
    // the value is there for use in the link fn
    isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope);
    }
    break;

case '=':
    if (optional && !attrs[attrName]) {
    return;
    }
    parentGet = $parse(attrs[attrName]);
    if (parentGet.literal) {
    compare = equals;
    } else {
    compare = function(a, b) { return a === b || (a !== a && b !== b); };
    }
    parentSet = parentGet.assign || function() {
    // reset the change, or we will throw this exception on every $digest
    lastValue = isolateBindingContext[scopeName] = parentGet(scope);
    throw $compileMinErr('nonassign',
        "Expression '{0}' used with directive '{1}' is non-assignable!",
        attrs[attrName], newIsolateScopeDirective.name);
    };
    lastValue = isolateBindingContext[scopeName] = parentGet(scope);
    var parentValueWatch = function parentValueWatch(parentValue) {
    if (!compare(parentValue, isolateBindingContext[scopeName])) {
        // we are out of sync and need to copy
        if (!compare(parentValue, lastValue)) {
        // parent changed and it has precedence
        isolateBindingContext[scopeName] = parentValue;
        } else {
        // if the parent can be assigned then do so
        parentSet(scope, parentValue = isolateBindingContext[scopeName]);
        }
    }
    return lastValue = parentValue;
    };
    parentValueWatch.$stateful = true;
    var unwatch;
    if (definition.collection) {
    unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
    } else {
    unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
    }
    isolateScope.$on('$destroy', unwatch);
    break;

case '&':
    parentGet = $parse(attrs[attrName]);
    isolateBindingContext[scopeName] = function(locals) {
    return parentGet(scope, locals);
    };
    break;
}

addDirective

function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,
                          endAttrName) {
      if (name === ignoreDirective) return null;
      var match = null;
      if (hasDirectives.hasOwnProperty(name)) {
        for (var directive, directives = $injector.get(name + Suffix),
            i = 0, ii = directives.length; i < ii; i++) {
          try {
            directive = directives[I];
            if ((maxPriority === undefined || maxPriority > directive.priority) &&
                 directive.restrict.indexOf(location) != -1) {
              if (startAttrName) {
                directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
              }
              tDirectives.push(directive);
              match = directive;
            }
          } catch (e) { $exceptionHandler(e); }
        }
      }
      return match;
    }

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352