前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >angularjs源码笔记(1.1)--directive compile

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

作者头像
alexqdjay
发布2022-01-04 16:57:07
1.7K0
发布2022-01-04 16:57:07
举报
文章被收录于专栏:alexqdjay

Compile (1)

1. 结构

compile跟其他service一样都需注册一个provider--CompileProvider就是compile注册进angular的provider。这样

主要的调用路径如下:

代码语言:javascript
复制
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阶段调用
代码语言:javascript
复制
// 将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];
  }
});
代码语言:javascript
复制
var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes,
                           maxPriority, ignoreDirective, previousCompileContext);
代码语言:javascript
复制
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn)

2.2. compileNodes()

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

1). 收集directives

代码语言:javascript
复制
directives = collectDirectives(nodeList[i]....);

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

代码语言:javascript
复制
nodeLinkFn = applyDirectivesToNode(directives, nodeList[i]....)

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

代码语言:javascript
复制
childLinkFn = compileNodes(childNodes...)

4). 返回 compositeLinkFn

2.3. applyDirectivesToNode()

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

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

代码语言:javascript
复制
linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);

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

代码语言:javascript
复制
module.directive('xxx', function () {
  return {
    compile: function () {
      return function postLinkFn() {};
    }
  };
});

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

代码语言:javascript
复制
{
  compile: function () {
    return {
      pre: function () {},
      post: function () {}
    };
  }
}

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

代码语言:javascript
复制
addLinkFns(...)

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

代码语言:javascript
复制
if (isFunction(linkFn)) {
  addLinkFns(null, linkFn, attrStart, attrEnd);
} else if (linkFn) {
  addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
}

3). 最后返回nodeLinkFn函数

3. Link阶段

代码语言:javascript
复制
compile.publicLinkFn -> compileNodes.compositeLinkFn -> applyDirectivesToNode.nodeLinkFn

3.1. publicLinkFn()

代码语言:javascript
复制
function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn)

1). 给每个element绑定了scope

代码语言:javascript
复制
// 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

代码语言:javascript
复制
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);

3.2. compositeLinkFn()

代码语言:javascript
复制
function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn)

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

代码语言:javascript
复制
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);
}
代码语言:javascript
复制
//有段细节的地方,为什么要复制一个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方法

代码语言:javascript
复制
// 对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执行

代码语言:javascript
复制
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 执行

代码语言:javascript
复制
// 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

代码语言:javascript
复制
childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);

4) postLink

代码语言:javascript
复制
// 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) 参数都一样

代码语言:javascript
复制
function link (scope, element, attrs, ctrls, transclude);

4. transclude

4.1 transclude的定义配置

先回忆下transclude配置

代码语言:javascript
复制
{
  transclude: true // or 'element'
}
  • 当配置element时,被transclude的是整个元素
  • 当配置true是,被transclude的只是该元素的子元素

4.2 transclude主要源码

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

代码语言:javascript
复制
{
  link: function (scope, el, attrs, ctrls, transclude) {
    transclude();
  }
}

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

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

代码语言:javascript
复制
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
                linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn);

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

代码语言:javascript
复制
// 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中调用,那么该参数也由此传入,代码如下

代码语言:javascript
复制
// 当该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()

代码语言:javascript
复制
// 配置 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中存在以下代码

代码语言:javascript
复制
childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);

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

由此传入publicLinkFn的parentBoundTranscludeFn

代码语言:javascript
复制
function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn)

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

代码语言:javascript
复制
else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
  childBoundTranscludeFn = parentBoundTranscludeFn;
}

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

4.4 应用

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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见下:

代码语言:javascript
复制
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源码笔记(1.2)--directive template

angularjs源码笔记(2)--inject

angularjs源码笔记(3)--scope

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016/08/16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Compile (1)
    • 1. 结构
      • 2. Compile阶段
        • 2.1. compile()
        • 2.2. compileNodes()
        • 2.3. applyDirectivesToNode()
      • 3. Link阶段
        • 3.1. publicLinkFn()
        • 3.2. compositeLinkFn()
        • 3.3. nodeLinkFn()
      • 4. transclude
        • 4.1 transclude的定义配置
        • 4.2 transclude主要源码
        • 4.3 transcludeFn的传承
        • 4.4 应用
    • 链接
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档