angluar浅析3 $CompileProvider

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

 
{{msg}}
 var app = angular.module('myApp', []);
app.directive("runoobDirective", function() {
    return {
        template : "

自定义指令!

" }; }); 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: 
          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;
    }

你可能感兴趣的:(angluar浅析3 $CompileProvider)