您的位置:首页 > Web前端 > AngularJS

angularjs源码笔记(1.1)--directive compile

2016-08-16 00:00 218 查看

Compile (1)

1. 结构

$compile跟其他service一样都需注册一个provider--$CompileProvider就是compile注册进angular的provider。这样$compile可以作为service被注入到其他方法的参数中。

主要的调用路径如下:

compile<1> -> compileNodes<2> -> applyDirectivesToNode<3>


<1> return publicLinkFn, 该fn中调用 <2>返回的fn

<2> return compositeLinkFn, 该fn中调用<3>返回的fn

<3> return nodeLinkFn

主线就是所说的compile阶段,而对返回的fn进行调用进入link阶段

2. Compile阶段

2.1. compile()

compile为入口fn,主要做3个事情,

包装node

调用compileNodes

返回publicLinkFn供link阶段调用

// 将text包装成<span>text</span>
forEach($compileNodes, function(node, index){
if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
$compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0];
}
});

var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes,
maxPriority, ignoreDirective, previousCompileContext);

return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn)


2.2. compileNodes()

参数会传入nodeList, 然后循环执行每个node,执行的事情如下:

1). 收集directives

directives = collectDirectives(nodeList[i]....);

2). 执行applyDirectivesToNode(后续详细分析)

nodeLinkFn = applyDirectivesToNode(directives, nodeList[i]....)

3). 递归调用执行childNodes上的compileNodes

childLinkFn = compileNodes(childNodes...)

4). 返回 compositeLinkFn

2.3. applyDirectivesToNode()

该fn的参数,(1) directives, (2)compileNode, 其他略

1). 即对collectDirectives收集过来directives数组依次编译(compile)compileNode

linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);

这里directive为定义的指令,如:

module.directive('xxx', function () {
return {
compile: function () {
return function postLinkFn() {};
}
};
});

return出来的object即为directive,上例可见compile返回出一个postLink的fn,当然完整的应该是一个包含preLink和postLink的object,如:

{
compile: function () {
return {
pre: function () {},
post: function () {}
};
}
}

2). 返回的linkFn进行收集,收集至preLinkFns和postLinkFns中,供后续调用

addLinkFns(...)

这边有个isFunction的判断,就是如果返回的只是function,然后就当作post收集,如果是object那么根据所属字段,pre还是post

if (isFunction(linkFn)) {
addLinkFns(null, linkFn, attrStart, attrEnd);
} else if (linkFn) {
addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
}

3). 最后返回nodeLinkFn函数

3. Link阶段

compile.publicLinkFn -> compileNodes.compositeLinkFn -> applyDirectivesToNode.nodeLinkFn


3.1. publicLinkFn()

function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn)

1). 给每个element绑定了scope

// Attach scope only to non-text nodes.
for(var i = 0, ii = $linkNode.length; i<ii; i++) {
var node = $linkNode[i],
nodeType = node.nodeType;
if (nodeType === 1 /* element */ || nodeType === 9 /* document */) {
$linkNode.eq(i).data('$scope', scope);
}
}

2). 调用之前返回的compositeLinkFn

if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);


3.2. compositeLinkFn()

function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn)

compositeLinkFn主要任务是执行applyDirectivesToNode返回的nodeLinkFn,以及递归调用compileNodes(childNodes)返回的compositeLinkFn

if (nodeLinkFn) {
//判断directive是不是定义的scope:true,进行处理
if (nodeLinkFn.scope) {
childScope = scope.$new();
$node.data('$scope', childScope);
} else {
childScope = scope;
}

//有关transclude的处理,后续分析
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;
}

nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);

} else if (childLinkFn) {
//childLinkFn === compositeLinkFn
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
}

//有段细节的地方,为什么要复制一个node数组出来呢?
//因为link阶段会对nodeList增加删除,会影响linkFn数组的执行
//复制出来数组能保证每个linkFn都会准确地执行
var nodeListLength = nodeList.length,
stableNodeList = new Array(nodeListLength);
for (i = 0; i < nodeListLength; i++) {
stableNodeList[i] = nodeList[i];
}


3.3. nodeLinkFn()

nodeLinkFn是执行之前众多directive的compile后收集的pre和post方法

// 对scope定义中@=&的解析,生成isolateScope
forEach(newIsolateScopeDirective.scope, function(definition, scopeName) {
var match = definition.match(LOCAL_REGEXP) || [],
attrName = match[3] || scopeName,
optional = (match[2] == '?'),
mode = match[1], // @, =, or &
lastValue,
parentGet, parentSet, compare;

isolateScope.$$isolateBindings[scopeName] = mode + attrName;

switch (mode) {

case '@':
break;

case '=':
break;

case '&':
break;

default:
throw $compileMinErr('iscp',
"Invalid isolate scope definition for directive '{0}'." +
" Definition: {... {1}: '{2}' ...}",
newIsolateScopeDirective.name, scopeName, definition);
}
})

接着以此执行controllerFns > preLinkFns > 递归childNodeLinkFn > postLinkFns

这就解释了dirtive中link,compile,ctrl顺序是 A.ctrl > A.preLink > a.ctrl > a.preLink > a.postLink > A.postLink

a是A的child-node

1)controllers执行

if (controllerDirectives) {
forEach(controllerDirectives, function(directive) {
var locals = {
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
$element: $element,
$attrs: attrs,
$transclude: transcludeFn
}, controllerInstance;

controller = directive.controller;
// 当配置controller: @ 时使用attr中配置的名字
if (controller == '@') {
controller = attrs[directive.name];
}

//实例化controller
controllerInstance = $controller(controller, locals);

elementControllers[directive.name] = controllerInstance;
if (!hasElementTranscludeDirective) {
$element.data('$' + directive.name + 'Controller', controllerInstance);
}

// 当配置controllerAs时将实例绑定到scope上
if (directive.controllerAs) {
locals.$scope[directive.controllerAs] = controllerInstance;
}
});
}

2) preLink 执行

// PRELINKING
for(i = 0, ii = preLinkFns.length; i < ii; i++) {
try {
linkFn = preLinkFns[i];
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn);
} catch (e) {
$exceptionHandler(e, startingTag($element));
}
}

getControllers()是用来获取directive中定义require的driective的ctrl

3) childLinkFn

childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);

4) postLink

// POSTLINKING
for(i = postLinkFns.length - 1; i >= 0; i--) {
try {
linkFn = postLinkFns[i];
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn);
} catch (e) {
$exceptionHandler(e, startingTag($element));
}
}

所有linkFn (pre和post) 参数都一样

function link (scope, element, attrs, ctrls, transclude);


4. transclude

4.1 transclude的定义配置

先回忆下transclude配置

{
transclude: true // or 'element'
}


当配置element时,被transclude的是整个元素

当配置true是,被transclude的只是该元素的子元素

4.2 transclude主要源码

又是一个调用链,最终调用入口在用户定义的link中,例如:

{
link: function (scope, el, attrs, ctrls, transclude) {
transclude();
}
}

那该参数是什么地方传入的?

截取nodeLinkFn中执行postLink的代码(preLink也一样,省略)

linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn);

就是最后那个参数,那么最后的那个参数到底是什么?

// boundTranscludeFn 是nodeLinkFn的参数
// function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn)
// 表明当存在boundTranscludeFn时,将controllersBoundTransclude赋值给transcludeFn
transcludeFn = boundTranscludeFn && controllersBoundTransclude;

//... (省略中间代码)

// 处理了两件事:
// 1、无参数或者一个参数时,scope=undefined
// 2、将该element上的controllers赋值给第三个参数
function controllersBoundTransclude(scope, cloneAttachFn) {
var transcludeControllers;

// no scope passed
if (arguments.length < 2) {
cloneAttachFn = scope;
scope = undefined;
}

if (hasElementTranscludeDirective) {
transcludeControllers = elementControllers;
}

return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers);
}

这么看link中传入的参数transcludeFn,其实还是nodeLinkFn的参数boundTranscludeFn,只是做了下参数处理

由上面分享可知,nodeLinkFn是在compositeLinkFn中调用,那么该参数也由此传入,代码如下

// 当该element就是定义了directive并且配置了transclude
// 调用createBoundTranscludeFn生成childBoundTranscludeFn,!注意!参数传入的是nodeLinkFn.transclude
if (nodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn);

}
// 当该elementd的parent定义了transclude的directive
// 直接使用父transcludeFn parentBoundTranscludeFn
else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
childBoundTranscludeFn = parentBoundTranscludeFn;

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

} else {
childBoundTranscludeFn = null;
}
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);

// ...

// transcludeFn 就是第一if情况中的nodeLinkFn.transclude
// previousBoundTranscludeFn 就是parentBoundTranscludeFn
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {

var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) {
var scopeCreated = false;

// 传入scope就使用传入的参数,没有就使用当前scope.$new
if (!transcludedScope) {
transcludedScope = scope.$new();
transcludedScope.$$transcluded = true;
scopeCreated = true;
}

var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn);
if (scopeCreated) {
clone.on('$destroy', function() { transcludedScope.$destroy(); });
}
return clone;
};

return boundTranscludeFn;
}

所以看代码知,处理了下scope,以及监听了$destroy事件进行销毁,然后就是调用传入的第二个参数transcludeFn

而transcludeFn就是nodeLinkFn.transclude,回到nodeLinkFn生成的地方--applyDirectivesToNode()

// 配置 transclude:'element'时是整个元素进行compile
// 配置 transclude: true时是子元素进行compile
if (directiveValue == 'element') {
hasElementTranscludeDirective = true;
terminalPriority = directive.priority;
$template = groupScan(compileNode, attrStart, attrEnd);
$compileNode = templateAttrs.$$element =
jqLite(document.createComment(' ' + directiveName + ': ' +
templateAttrs[directiveName] + ' '));
compileNode = $compileNode[0];
replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode);

// 递归调用compile返回publicLinkFn
// 传入当前directive的priority,作为终止priority防止死循环
childTranscludeFn = compile($template, transcludeFn, terminalPriority,
replaceDirective && replaceDirective.name, {
nonTlbTranscludeDirective: nonTlbTranscludeDirective
});
}
else {
$template = jqLite(jqLiteClone(compileNode)).contents();
$compileNode.empty(); // clear contents
childTranscludeFn = compile($template, transcludeFn);
}

// ...

nodeLinkFn.transclude = childTranscludeFn;

因此,childTranscludeFn其实就是compile返回的publicLinkFn,分析结论:transcludeFn其实就是调用publicLinkFn

4.3 transcludeFn的传承

当template中含有directive时如何在该子directive的link中获取到$transclude(即parent的原有childNode的publicLinkFn)来调用

在nodeLinkFn中存在以下代码

childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);

该boundTranscludeFn是没有经过controllersBoundTransclude()包装过因为每个element的directive对应的controllers不同需要现用现调

由此传入publicLinkFn的parentBoundTranscludeFn

function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn)

然后在compositeLinkFn中洗白成childBoundTranscludeFn,最终流入到link的参数$transclude供使用

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

nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);


4.4 应用

由此延展,当定义了transclude的directive,link方法中可以调用transcludeFn来获取compile和link后的子元素,例如

directive('myDir', function () {
return {
transclude: true,
replace: true,
template: '<div class="my-dir"></div>'
link: function (scope, element, attrs, ctrls, transcludeFn) {
var childNodes = transcludeFn(scope);
childNodes.addClass('my-child-nodes');
element.append(childNodes);
}
}
});

/** before

<my-dir>
<div>1</div>
<div>2</div>
<div>3</div>
</my-dir>

**/

/** after

<div class="my-dir">
<div class="my-child-nodes">1</div>
<div class="my-child-nodes">2</div>
<div class="my-child-nodes">3</div>
</div>

**/

可以联想到ng-transclude

var ngTranscludeDirective = ngDirective({
link: function($scope, $element, $attrs, controller, $transclude) {
if (!$transclude) {
throw minErr('ngTransclude')('orphan',
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}',
startingTag($element));
}

$transclude(function(clone) {
$element.empty();
$element.append(clone);
});
}
});

这里使用到cloneFn,关于cloneFn见下:

var $linkNode = cloneConnectFn
? JQLitePrototype.clone.call($compileNodes)
: $compileNodes;

// ...

if (cloneConnectFn) cloneConnectFn($linkNode, scope);
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
return $linkNode;


进行jq的clone

调用cloneFn

这边我有个疑问:为什么要先clone下呢?望知道的指点下,谢谢!

链接

angularjs源码笔记(1.1)--directive compile

angularjs源码笔记(1.2)--directive template

angularjs源码笔记(2)--inject

angularjs源码笔记(3)--scope
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  AngularJS