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 compileangularjs源码笔记(1.2)--directive template
angularjs源码笔记(2)--inject
angularjs源码笔记(3)--scope
相关文章推荐
- angularjs源码笔记(2)--loader modules
- angularjs源码笔记(4)--scope
- AngularJS学习笔记--002--Angular JS路由插件ui.router源码解析
- angularjs源码笔记(3)--injector
- angularjs源码笔记(1.3)--directive ctrl & attrs
- angularjs源码笔记(1.2)--directive template
- angularjs源码笔记(5.1)--parse
- ASP.net(1.1)原理学习笔记--第八章 自定义控件Custom Controls
- DotLucene源码浅读笔记(1) : Lucene.Net.Analysis
- ASP.net(1.1)原理学习笔记--第九章 缓存Caching
- Inside the C++ Object Model学习笔记[Chap1.1]
- “Multithreaded Job Queue”源码阅读笔记
- ASP.net(1.1)原理学习笔记--第四章 HTTP管道 Pipeline
- 日历控件源码开放--适用于ASP.NET 1.1
- ASP.net(1.1)原理学习笔记--第十章 状态管理State Management
- SGI STL V3.2 源码剖析笔记-1. 空间配置器
- SGI STL V3.2 源码剖析笔记-3. vector(未完待续)
- ASP.net(1.1)原理学习笔记--第二章 web Form 网页视窗
- Struts 源码学习笔记
- ASP.net(1.1)原理学习笔记--第六章 验证 Validation