Angular.js源码分析之link

在angular初始化的最后阶段有这样的一段代码:

compile(element)(scope);

上一篇大概分析了下compile,那么这次继续分析下一步也就是调用传入scope,其实这一步也就是所谓的link。

回顾上篇中说了compile的返回的结果是一个publicLinkFn函数,然后link阶段其实也就是调用这个函数了:

return function publicLinkFn(scope, cloneConnectFn, options) {
  assertArg(scope, 'scope');

  options = options || {};
  var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
    transcludeControllers = options.transcludeControllers,
    futureParentElement = options.futureParentElement;

  // When `parentBoundTranscludeFn` is passed, it is a
  // `controllersBoundTransclude` function (it was previously passed
  // as `transclude` to directive.link) so we must unwrap it to get
  // its `boundTranscludeFn`
  if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
    parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
  }

  if (!namespace) {
    namespace = detectNamespaceForChildElements(futureParentElement);
  }
  var $linkNode;
  if (namespace !== 'html') {
    // When using a directive with replace:true and templateUrl the $compileNodes
    // (or a child element inside of them)
    // might change, so we need to recreate the namespace adapted compileNodes
    // for call to the link function.
    // Note: This will already clone the nodes...
    $linkNode = jqLite(
      wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
    );
  } else if (cloneConnectFn) {
    // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
    // and sometimes changes the structure of the DOM.
    // 因为在处理transclude的时候也会compile
    // 而返回的那个函数transcludeFn被调用的时候 会传入第二个参数
    // 因为transcludeFn可能会被调用多次 所以这里需要clone一份
    $linkNode = JQLitePrototype.clone.call($compileNodes);
  } else {
    $linkNode = $compileNodes;
  }

  if (transcludeControllers) {
    for (var controllerName in transcludeControllers) {
      $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance);
    }
  }

  compile.$$addScopeInfo($linkNode, scope);
	
	// 其实就是transclude传入的函数
  if (cloneConnectFn) cloneConnectFn($linkNode, scope);
  // 执行compositeLinkFn函数 里边会执行所有的link函数
  // link 到scope
  if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
  return $linkNode;
};

可以看到调用了在上一篇compile中compileNodes返回的处理所有节点的link函数,来看下具体逻辑:

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

  if (nodeLinkFnFound) {
    // 复制一份
    // 怕nodeLinkFn会动当前元素的元素内容
    // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our
    // offsets don't get screwed up
    var nodeListLength = nodeList.length;
    stableNodeList = new Array(nodeListLength);

    // create a sparse array by only copying the elements which have a linkFn
    for (i = 0; i < linkFns.length; i+=3) {
      idx = linkFns[i];
      stableNodeList[idx] = nodeList[idx];
    }
  } else {
    stableNodeList = nodeList;
  }
	// 遍历收集的所有节点的linkFns
  for (i = 0, ii = linkFns.length; i < ii;) {
    node = stableNodeList[linkFns[i++]];
    nodeLinkFn = linkFns[i++];
    childLinkFn = linkFns[i++];

    if (nodeLinkFn) {
      if (nodeLinkFn.scope) {
        // 有scope的话 并且为true 就要创建新的scope
        // 而独立scope的scope则是在nodeLinkFn执行的时候创建的
        childScope = scope.$new();
        // 给节点增加scope信息
        // 然后通过元素的scope就能得到元素所在的scope了
        compile.$$addScopeInfo(jqLite(node), childScope);
        // 记得destroy的时候销毁
        var destroyBindings = nodeLinkFn.$$destroyBindings;
        if (destroyBindings) {
          nodeLinkFn.$$destroyBindings = null;
          childScope.$on('$destroyed', destroyBindings);
        }
      } else {
      	// 直接使用当前scope
        childScope = scope;
      }

      // 创建childBoundTranscludeFn
      // 这个childBoundTranscludeFn函数会执行nodeLinkFn.transclude
      // 但是会为transclude单独创建的一个scope
      if (nodeLinkFn.transcludeOnThisElement) {
        childBoundTranscludeFn = createBoundTranscludeFn(
            scope, nodeLinkFn.transclude, parentBoundTranscludeFn);

      } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
        childBoundTranscludeFn = parentBoundTranscludeFn;

      } else if (!parentBoundTranscludeFn && transcludeFn) {
        childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);

      } else {
        childBoundTranscludeFn = null;
      }
      // 独立scope的创建新的scope的逻辑是在applyDirectivesToNode返回的nodeLinkFn
      // 中创建的
      nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn,
                 nodeLinkFn);

    } else if (childLinkFn) {
      // 没有nodeLinkFn 但是有childLinkFn 那么直接执行childLinkFn
      // 因为其实在nodeLinkFn中也是会执行childLinkFn的
      childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
    }
  }
}
// 给变换transclude创建新的scope
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {

  var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {

    if (!transcludedScope) {
      transcludedScope = scope.$new(false, containingScope);
      transcludedScope.$$transcluded = true;
    }

    return transcludeFn(transcludedScope, cloneFn, {
      parentBoundTranscludeFn: previousBoundTranscludeFn,
      transcludeControllers: controllers,
      futureParentElement: futureParentElement
    });
  };

  return boundTranscludeFn;
}

下一步就是分析下单个节点的link函数nodeLinkFn了:

// 返回的 nodeLinkFn
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn, thisLinkFn) {
  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) {
    // 创建独立scope
    isolateScope = scope.$new(true);
  }

  if (boundTranscludeFn) {
    // transcludeFn处理
    // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn`
    // is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
    // 见下边的controllersBoundTransclude定义
    transcludeFn = controllersBoundTransclude;
    transcludeFn.$$boundTransclude = boundTranscludeFn;
  }

  // controllers处理
  if (controllerDirectives) {
  	// 根据收集到的controllerDirectives来得到对应的controller实例
    elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
  }

  if (newIsolateScopeDirective) {
    // Initialize isolate scope bindings for new isolate scope directive.
    compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
        templateDirective === newIsolateScopeDirective.$$originalDirective)));
    compile.$$addScopeClass($element, true);
    isolateScope.$$isolateBindings =
        newIsolateScopeDirective.$$isolateBindings;
    // 参见 处理指令绑定
    initializeDirectiveBindings(scope, attrs, isolateScope,
                                isolateScope.$$isolateBindings,
                                newIsolateScopeDirective, isolateScope);
  }
  if (elementControllers) {
  	// 处理独立Scope或者新的继承Scope
    var scopeDirective = newIsolateScopeDirective || newScopeDirective;
    var bindings;
    var controllerForBindings;
    if (scopeDirective && elementControllers[scopeDirective.name]) {
    	// bindToController绑定
      bindings = scopeDirective.$$bindings.bindToController;
      controller = elementControllers[scopeDirective.name];

      if (controller && controller.identifier && bindings) {
        controllerForBindings = controller;
        thisLinkFn.$$destroyBindings =
            initializeDirectiveBindings(scope, attrs, controller.instance,
                                        bindings, scopeDirective);
      }
    }
    for (i in elementControllers) {
      controller = elementControllers[i];
      var controllerResult = controller();

      if (controllerResult !== controller.instance) {
        // 重写掉controller的instance
        controller.instance = controllerResult;
        $element.data('$' + i + 'Controller', controllerResult);
        if (controller === controllerForBindings) {
        	// 此时也就意味着是上边有bindToController的情况
        	// 由于controller的instance的实例都变了 所以这里需要重新初始化
          // Remove and re-install bindToController bindings
          thisLinkFn.$$destroyBindings();
          thisLinkFn.$$destroyBindings =
            initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective);
        }
      }
    }
  }

  // PRELINKING
  // 先执行preLinkFns中的函数
  for (i = 0, ii = preLinkFns.length; i < ii; i++) {
    linkFn = preLinkFns[i];
    invokeLinkFn(linkFn,
        linkFn.isolateScope ? isolateScope : scope,
        $element,
        attrs,
        // 可以看到如果有require的话 会得到require依赖的对应的controller
        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.
  // 然后执行子节点的link函数
  var scopeToChild = scope;
  if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {
    scopeToChild = isolateScope;
  }
  childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);

  // POSTLINKING
  // 最后执行自身的postLinkFns
  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
    );
  }

  // 注入$transclude
  // This is the function that is injected as `$transclude`.
  // Note: all arguments are optional!
  function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
    var transcludeControllers;

    // No scope passed in:
    if (!isScope(scope)) {
      futureParentElement = cloneAttachFn;
      cloneAttachFn = scope;
      scope = undefined;
    }

    if (hasElementTranscludeDirective) {
      transcludeControllers = elementControllers;
    }
    if (!futureParentElement) {
      futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
    }
    return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
  }
}
// 设置controller 主要依赖 $controller
function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
  var elementControllers = createMap();
  for (var controllerKey in controllerDirectives) {
    var directive = controllerDirectives[controllerKey];
    // 在controller中可以注入的东西
    var locals = {
      $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
      $element: $element,
      $attrs: attrs,
      $transclude: transcludeFn
    };

    var controller = directive.controller;
    if (controller == '@') {
      // ngController设置的controller的值是@
      // 需要找回属性中的该值
      controller = attrs[directive.name];
    }

    // 调用$controller服务 但是 是延迟的
    var controllerInstance = $controller(controller, locals, true, directive.controllerAs);

    // For directives with element transclusion the element is a comment,
    // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
    // clean up (http://bugs.jquery.com/ticket/8335).
    // Instead, we save the controllers for the element in a local hash and attach to .data
    // later, once we have the actual element.
    elementControllers[directive.name] = controllerInstance;
    if (!hasElementTranscludeDirective) {
      $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
    }
  }
  return elementControllers;
}

上边有两个关键的函数initializeDirectiveBindingsinvokeLinkFn,有必要依次来看:

// 设置独立Scope和controller绑定,主要是用watch达到目的
// 传闻中的三种绑定策略 @ & =
// 只处理独立Scope和带有controllerAs语法的继承Scope
/**
 * @param scope       父级scope
 * @param attrs       属性对象
 * @param destination 目标实例(独立scope或者controller实例)
 * @param bindings    定义的绑定规则
 * @param directive   指令
 * @param newScope    独立scope
 */
function initializeDirectiveBindings(scope, attrs, destination, bindings, directive, newScope) {
  var onNewScopeDestroyed;
  // 循环处理
  forEach(bindings, function(definition, scopeName) {
    var attrName = definition.attrName,
    optional = definition.optional,
    mode = definition.mode, // @, =, or &
    lastValue,
    parentGet, parentSet, compare;

    switch (mode) {

      case '@':
      	// 直接取attr上的属性值 字符串处理
        if (!optional && !hasOwnProperty.call(attrs, attrName)) {
          destination[scopeName] = attrs[attrName] = void 0;
        }
        // 监控下 如果说属性值发生变化了还是要同步的
        attrs.$observe(attrName, function(value) {
          if (isString(value)) {
            destination[scopeName] = value;
          }
        });
        attrs.$$observers[attrName].$$scope = scope;
        if (isString(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
          destination[scopeName] = $interpolate(attrs[attrName])(scope);
        }
        break;

      case '=':
      	// 将父级scope中attrs中attrName的变量绑定到现有scope
      	// 的scopeName上
        if (!hasOwnProperty.call(attrs, attrName)) {
          if (optional) break;
          attrs[attrName] = void 0;
        }
        if (optional && !attrs[attrName]) break;

        parentGet = $parse(attrs[attrName]);
        if (parentGet.literal) {
          compare = equals;
        } else {
          compare = function(a, b) { return a === b || (a !== a && b !== b); };
        }
        // 当子scope上的值发生改变了 父级scope上的值也要设置下
        parentSet = parentGet.assign || function() {
          // reset the change, or we will throw this exception on every $digest
          lastValue = destination[scopeName] = parentGet(scope);
          throw $compileMinErr('nonassign',
              "Expression '{0}' used with directive '{1}' is non-assignable!",
              attrs[attrName], directive.name);
        };
        // 保留上次的值
        lastValue = destination[scopeName] = parentGet(scope);
        var parentValueWatch = function parentValueWatch(parentValue) {
        	// 判断更新destination[scopeName]
          if (!compare(parentValue, destination[scopeName])) {
            // we are out of sync and need to copy
            if (!compare(parentValue, lastValue)) {
              // parent changed and it has precedence
              destination[scopeName] = parentValue;
            } else {
              // if the parent can be assigned then do so
              parentSet(scope, parentValue = destination[scopeName]);
            }
          }
          return lastValue = parentValue;
        };
        parentValueWatch.$stateful = true;
        var unwatch;
				
				// watch变化
        if (definition.collection) {
          unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
        } else {
          unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
        }
        onNewScopeDestroyed = (onNewScopeDestroyed || []);
        onNewScopeDestroyed.push(unwatch);
        break;

      case '&':
      	// 绑定策略 作为函数去调用
        // Don't assign Object.prototype method to scope
        parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;

        // Don't assign noop to destination if expression is not valid
        if (parentGet === noop && optional) break;
				
        destination[scopeName] = function(locals) {
          return parentGet(scope, locals);
        };
        break;
    }
  });
  // 一定要有相应的解除绑定方法
  var destroyBindings = onNewScopeDestroyed ? function destroyBindings() {
    for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) {
      onNewScopeDestroyed[i]();
    }
  } : noop;
  if (newScope && destroyBindings !== noop) {
    newScope.$on('$destroy', destroyBindings);
    return noop;
  }
  return destroyBindings;
}

然后执行link函数的:

function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
  try {
    // link函数的执行 参数是固定的
    // scope ele attrs controllers 以及 transcludeFn
    linkFn(scope, $element, attrs, controllers, transcludeFn);
  } catch (e) {
    $exceptionHandler(e, startingTag($element));
  }
}

结语

上边的分析其实就是整个link的过程,从代码中可以看出,整个link的过程其实是preLinkFns -> childLinkFn -> postLinkFns,其实像是一个递归过程。所以说preLinkFns的执行过程是由浅到深,而postLinkFns的执行过程则是由深到浅。

发布于: 2015年 10月 24日