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;
}