博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
angularjs源码笔记(1.1)--directive compile
阅读量:6947 次
发布时间:2019-06-27

本文共 13083 字,大约阅读时间需要 43 分钟。

hot3.png

Compile (1)

1. 结构

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

主要的调用路径如下:

compile<1> -> compileNodes<2> -> applyDirectivesToNode<3>
  1. <1> return publicLinkFn, 该fn中调用 <2>返回的fn
  2. <2> return compositeLinkFn, 该fn中调用<3>返回的fn
  3. <3> return nodeLinkFn

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

2. Compile阶段

2.1. compile()

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

  1. 包装node
  2. 调用compileNodes
  3. 返回publicLinkFn供link阶段调用
// 将text包装成textforEach($compileNodes, function(node, index){  if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {    $compileNodes[index] = node = jqLite(node).wrap('').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进行收集,收集至preLinkFnspostLinkFns中,供后续调用

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

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后收集的prepost方法

// 对scope定义中@=&的解析,生成isolateScopeforEach(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 执行

// PRELINKINGfor(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

// POSTLINKINGfor(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赋值给transcludeFntranscludeFn = 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.transcludeif (nodeLinkFn.transcludeOnThisElement) {  childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn);} // 当该elementd的parent定义了transclude的directive// 直接使用父transcludeFn parentBoundTranscludeFnelse 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 就是parentBoundTranscludeFnfunction 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时是子元素进行compileif (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: '
' link: function (scope, element, attrs, ctrls, transcludeFn) { var childNodes = transcludeFn(scope); childNodes.addClass('my-child-nodes'); element.append(childNodes); } }});/** before
1
2
3
**//** after
1
2
3
**/

可以联想到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;
  1. 进行jq的clone
  2. 调用cloneFn

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

链接

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

angularjs源码笔记(2)--inject

angularjs源码笔记(3)--scope

转载于:https://my.oschina.net/alexqdjay/blog/733304

你可能感兴趣的文章
5月3日云栖精选夜读丨寒武纪重磅发布首款AI云芯片,阿里专家告诉你必须注意的Java编程细节...
查看>>
机器学习从业人员到底做什么?
查看>>
MyBatis mapper.xml处理sql中的 大于,小于,大于等于,小于等于
查看>>
java 受检异常和非受检异常
查看>>
GC垃圾回收机制
查看>>
rsync通过服务同步、linux系统日志
查看>>
Redlock:Redis分布式锁最牛逼的实现
查看>>
一篇文章带你解析,乐观锁与悲观锁的优缺点
查看>>
阿里云如何打破Oracle迁移上云的壁垒
查看>>
小技巧:如何突破某些网站只能登陆后才能进行文字拷贝的限制
查看>>
Spring Boot教程(十八)使用Spring StateMachine框架实现状态机
查看>>
区块链如何应用于保险行业
查看>>
自然语言处理工具HanLP被收录中国大数据产业发展的创新技术新书《数据之翼》...
查看>>
五周第三次课(4月20日)8.1 shell介绍 8.2 命令历史 8.3 命令补全和别名 8.4 通配符 8.5 输入输出重定向...
查看>>
Dubbo Mesh 在闲鱼生产环境中的落地实践
查看>>
用idea制作Javaweb程序遇到的过程
查看>>
一台Java服务器怎样跑多少个线程
查看>>
想要成为python大神,这17个老司机收藏的国外免费学习网站不可错过!
查看>>
各种按钮的样式
查看>>
GoJS教程[2019]:使用GraphObjects构建零件
查看>>