Angularjs理解二

1.dom加载完毕,找寻ng-app,先从上到下找相关的指令,然后分两阶段执行。先找到所有的指令,完成编译,得到一个个链接函数,最后在链接到一个个controller上。(还是边编译边链接??)
先执行:

injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',  
  function(scope, element, compile, injector, animate) {
    scope.$apply(function() {
      element.data('$injector', injector);
      compile(element)(scope);
    });
  }
]);

关键就是编译完成,得到连接函数,然后链接。

function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {  
  // 对于文本节点,使用<span>包装
  forEach($compileNodes, function(node, index) {
    if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
       $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
    }
  });

  // 调用compileNodes编译当前节点,返回链接函数
  var compositeLinkFn = 
        compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext);

  // 添加scope class
  compile.$$addScopeClass($compileNodes);
  var namespace = null;
  return function publicLinkFn(scope, cloneConnectFn, options) {
    // 后面再看
  };   

其中有个技巧:
forEach,查看
没有,[]与forEach,可以使用apply、call进行借用。但是ie不支持,chrome、ff支持。

if (!Array.prototype.forEach) {  
    Array.prototype.forEach = function(callback, thisArg) {  
        var T, k;  
        if (this == null) {  
            throw new TypeError(" this is null or not defined");  
        }  
        var O = Object(this);  
        var len = O.length >>> 0; // Hack to convert O.length to a UInt32  
        if ({}.toString.call(callback) != "[object Function]") {  
            throw new TypeError(callback + " is not a function");  
        }  
        if (thisArg) {  
            T = thisArg;  
        }  
        k = 0;  
        while (k < len) {  
            var kValue;  
            if (k in O) {  
                kValue = O[k];  
                callback.call(T, kValue, k, O);  
            }  
            k++;  
        }  
    };  
}

.可在css、注释中使用。
.jquery.wrap,就用后方的内容,包裹前方的内容。

其中编译,搜集本节点和父节点所有的指令,然后应用,然后得到指令的链接函数,然后在放到一个数组中。

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();
  // 收集每个节点上的所有指令
  directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, ignoreDirective);
  // 应用指令,返回链接函数
  nodeLinkFn = (directives.length)
      ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], [], previousCompileContext)
      : null;
  // 添加scope class
  if (nodeLinkFn && nodeLinkFn.scope) {
     compile.$$addScopeClass(attrs.$$element);
  }
  // 如果父亲节点没有链接函数或者已终止(terminal)
  // 或者没有孩子节点
  // 孩子节点的链接函数就为null
  // 否则递归编译孩子节点
  childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
          !(childNodes = nodeList[i].childNodes) ||
          !childNodes.length)
     ? null
     : compileNodes(childNodes,
       nodeLinkFn ? (
        (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
         && nodeLinkFn.transclude) : transcludeFn);
  // 将当前节点的链接函数和孩子节点的链接函数都插入到linkFns数组中
  if (nodeLinkFn || childLinkFn) {
     linkFns.push(i, nodeLinkFn, childLinkFn);
     linkFnFound = true;
     nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
  }
  previousCompileContext = null;
  }
  // 如果有链接函数返回闭包(compositeLinkFn能访问linkFns)
  return linkFnFound ? compositeLinkFn : null;
  function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
  // 代码略,后面再说
  }
}

编译节点的主要流程为两步,第一步通过 collectDirectives 搜集当前节点的指令,第二步调用 applyDirectivesToNode 来应用指令,最后调用 compileNodes 函数递归编译当前节点的子节点,最后把所有的函数添加到一个内部的 linkFns 数组中,这个数组将在最后链接的时候用到。

收集指令

Angular收集指令是根据节点类型来的,一共有三种类型:

1.element node:先根据tagName使用 addDirective 来添加指令,然后遍历节点的attrs使用 addAttrInterpolateDirective 和 addDirective 来添加指令,最后通过className使用 addDirective 来添加指令

2.text node:调用 addTextInterpolateDirective 方法来添加指令

3.comment node:匹配注释,使用 addDirective 添加指令

收集指令的过程主要用到了三个方法: addDirective 、 addAttrInterpolateDirective 和 addTextInterpolateDirective

首先来看 addDirective 方法:

function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName) {  
  // 是被忽略的指令,返回null
  if (name === ignoreDirective) return null;
  var match = null;
  // hasDirectives系统在初始化的时候添加的一个内健指令对象集合,$injector
  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数组中
           tDirectives.push(directive);
           match = directive;
         }
      } catch (e) { $exceptionHandler(e); }
    }
  }
  return match;
}
如果是文本节点就创建内部指令,监听scope变化然后设置节点的值

function addTextInterpolateDirective(directives, text) {  
  var interpolateFn = $interpolate(text, true);
  if (interpolateFn) {
    directives.push({
      priority: 0,
      compile: function textInterpolateCompileFn(templateNode) {
        var templateNodeParent = templateNode.parent(),
            hasCompileParent = !!templateNodeParent.length;

        if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);

        return function textInterpolateLinkFn(scope, node) {
          var parent = node.parent();
          if (!hasCompileParent) compile.$$addBindingClass(parent);
          compile.$$addBindingInfo(parent, interpolateFn.expressions);
          scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
            node[0].nodeValue = value;
          });
        };
      }
    });
  }
}
像如下的文本节点就会自动添加上面的指令,自动添加一个监听,通过原生方法来修改节点的值

<body>  
    {{ feenan }}
</body>  
应用指令到当前节点

收集完所有指令之后,接下来就要调用 applyDirectivesToNode 方法来将指令应用到当前节点上了,这个方法将会生成用于链接阶段的link函数。

这个函数比较复杂,我们拆开来看,其主体如下:

// 遍历当前节点的每个指令
for (var i = 0, ii = directives.length; i < ii; i++) {  
  // 1.判断scope类型
  // 2.判断是否需要有controller
  // 3.transclude的处理
  // 4.template的处理
  // 5.templateurl的处理
  // 6.compile处理
  // 7.terminal处理
}
// return 收集到的信息-nodeLinkFn
下面逐步来看看编译的完整过程,首先说明一下:当前节点 $compileNode -即要编译的节点

1.判断scope类型

if (directiveValue = directive.scope) {  
  // 跳过需要异步处理的指令,模板加载完成之后再处理
  if (!directive.templateUrl) {
    // scope属性为对象,例如{},则需要创建独立作用域
    if (isObject(directiveValue)) {
      newIsolateScopeDirective = directive;
    }
  }
  newScopeDirective = newScopeDirective || directive;
}
2.判断是否需要controller

// 同样,跳过要异步处理的指令,模板加载完成之后再处理
if (!directive.templateUrl && directive.controller) {  
  directiveValue = directive.controller;
  // 收集当前节点上所用要创建的controller
  controllerDirectives = controllerDirectives || createMap();
  controllerDirectives[directiveName] = directive;
}
3.transclude的处理

if (directiveValue = directive.transclude) {  
  hasTranscludeDirective = true;
  // element
  if (directiveValue == 'element') {
    hasElementTranscludeDirective = true;
    terminalPriority = directive.priority;
    $template = $compileNode;
    // 删除当前节点,替换为注释
    $compileNode = templateAttrs.$$element =
        jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' '));
    compileNode = $compileNode[0];
    replaceWith(jqCollection, sliceArgs($template), compileNode);

    // 编译当前节点
    childTranscludeFn = compile($template, transcludeFn, terminalPriority,  replaceDirective && replaceDirective.name, { nonTlbTranscludeDirective: nonTlbTranscludeDirective});
  } else { // true
    // 复制当前节点内容
    $template = jqLite(jqLiteClone(compileNode)).contents();
    // 清空当前节点
    $compileNode.empty();
    // 编译复制的内容
    childTranscludeFn = compile($template, transcludeFn);
  }
}
4.template的处理

if (directive.template) {  
  hasTemplate = true;
  templateDirective = directive;

  // 如果template为函数,则函数返回值为模板内容
  // 否则就是字符串,那么字符串就是模板内容
  directiveValue = (isFunction(directive.template))
      ? directive.template($compileNode, templateAttrs)
      : directive.template;

  directiveValue = denormalizeTemplate(directiveValue);

  // 如果replace为true
  if (directive.replace) {
    replaceDirective = directive;
    if (jqLiteIsTextNode(directiveValue)) {
      $template = [];
    } else {
      $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
    }
    // 模板的第一个节点
    compileNode = $template[0];

    if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
      // 没有要编译的内容或不是有效节点,报错
    }

    // 1.模板的第一个节点(compileNode)替换当前节点($compileNode)
    replaceWith(jqCollection, $compileNode, compileNode);

    var newTemplateAttrs = {$attr: {}};

    // 2.收集模板的第一个节点(compileNode)的所有指令
    var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
    // 3.当前节点($compileNode)剩余未编译的指令
    var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));

    if (newIsolateScopeDirective) {
      markDirectivesAsIsolate(templateDirectives);
    }
    // 4.$compileNode与compileNode的指令合并
    directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
    // 5.将$compileNode与compileNode的属性合并
    mergeTemplateAttributes(templateAttrs, newTemplateAttrs);

    ii = directives.length;
  } else {
    // replace为false,直接将模板内容插入当前节点即可
    $compileNode.html(directiveValue);
  }
}
5.templateurl的处理

if (directive.templateUrl) {  
  hasTemplate = true;
  templateDirective = directive;

  if (directive.replace) {
    replaceDirective = directive;
  }

  // 和template的处理类似,只是在获取到模板之前,这些节点编译挂起
  // 等获取到模板内容之后再继续编译
  nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
      templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
        controllerDirectives: controllerDirectives,
        newIsolateScopeDirective: newIsolateScopeDirective,
        templateDirective: templateDirective,
        nonTlbTranscludeDirective: nonTlbTranscludeDirective
      });
  ii = directives.length;
}
6.compile处理

// 同样,跳过需要异步处理的指令
if (!directive.templateUrl && directive.compile) {  
  try {
    // 使用指令的compile函数编译指令
    linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);

    // 如果返回的是函数,则该函数为post-link函数
    // 否则返回为对象,pre和post属性分别对应指令的pre-link和post-link函数
    if (isFunction(linkFn)) {
      addLinkFns(null, linkFn, attrStart, attrEnd);
    } else if (linkFn) {
      addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
    }
  } catch (e) {
    $exceptionHandler(e, startingTag($compileNode));
  }
}
7.terminal处理

// 有终止属性的指令,优先级小于当前指令的不会被编译
if (directive.terminal) {  
  nodeLinkFn.terminal = true;
  terminalPriority = Math.max(terminalPriority, directive.priority);
}
编译阶段收集了足够的信息,最后返回 nodeLinkFn 函数及其属性,在链接过程中就可以使用了:

nodeLinkFn = {  
  scope = newScopeDirective && newScopeDirective.scope === true
  transcludeOnThisElement = hasTranscludeDirective
  elementTranscludeOnThisElement = hasElementTranscludeDirective
  templateOnThisElement = hasTemplate
  transclude = childTranscludeFn
}
publicLinkFn(scope)

这里传进来的 scope 是 $rootScope ,这个方法主要是执行所有的链接函数,创建scope,添加监听函数:

function publicLinkFn(scope, cloneConnectFn, options) {  
  // 略去一大推
  if (cloneConnectFn) cloneConnectFn($linkNode, scope);
  if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
  return $linkNode;
};
这个函数实际调用的是 compositeLinkFn 函数,而 compositeLinkFn 函数是 compileNodes 函数的返回值,实际上它返回了一个闭包。 compositeLinkFn 函数操作的是 linkFns , linkFns 包含两部分:当前节点的链接函数 nodeLinkFn 和子节点的链接函数 childLinkFn ,而 childLinkFn 本身也是一个 compositeLinkFn (在子节点上递归调用 compileNodes 的返回结果),所以实际的链接过程就是递归调用 nodelinkFn 函数:

// 1.创建独立scope
if (newIsolateScopeDirective) {  
   isolateScope = scope.$new(true);
}
// 2.创建控制器
if (controllerDirectives) {  
   elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);

  for (i in elementControllers) {
    controller = elementControllers[i];
    var controllerResult = controller();
    // 略
  }
}

// 3.pre-link
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
  );
}

// 4.递归执行子节点的链接函数
var scopeToChild = scope;  
if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {  
   scopeToChild = isolateScope;
}
childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);

// 5.post-link
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
  );
}
执行流程为 preLinkFns -> childLinkFn -> postLinkFns ,这些信息都已经在第一步编译的时候收集好了,链接的时候使用即可。所以pre-link的执行顺序和文档节点顺序相同,而post-link是在递归回溯的过程中执行的,因此正好和文档节点顺序相反,后面会对编译连接过程给出一个小例子。

这里补充说一下如何获取节点上的所有控制器,看代码:

function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {  
  var elementControllers = createMap();
  for (var controllerKey in controllerDirectives) {
    var directive = controllerDirectives[controllerKey];
    var locals = {
      $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
      $element: $element,
      $attrs: attrs,
      $transclude: transcludeFn
    };

    var controller = directive.controller;
    // 特殊处理ng-controller
    if (controller == '@') {
      controller = attrs[directive.name];
    }

    // 生成控制器实例,实际是返回已经注入依赖的工厂函数
    var controllerInstance = $controller(controller, locals, true, directive.controllerAs);

    // 含有 transclude 指令的元素是注释
    // jQuery不支持在注释节点设置data
    // 因此,暂时将controller设置在local hash中
    // 当 transclude 完成后,生成真正的节点之后,再将controller设置到data中
    elementControllers[directive.name] = controllerInstance;
    if (!hasElementTranscludeDirective) {
      $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
    }
  }
  return elementControllers;
}

参考文献http://www.tuicool.com/articles/3QjyMvm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值