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;
}
上边有两个关键的函数initializeDirectiveBindings
和invokeLinkFn
,有必要依次来看:
// 设置独立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
的执行过程则是由深到浅。