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包装成<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进行收集,收集至preLinkFnspostLinkFns中,供后续调用

addLinkFns(...)

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

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

3). 最后返回nodeLinkFn函数

3. Link阶段

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

3.1.publicLinkFn()

function publicLinkFn(scope,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,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,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定义中@=&的解析,生成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,elementControllers),transcludeFn);
  } catch (e) {
    $exceptionHandler(e,startingTag($element));
  }
}

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

3) childLinkFn

childLinkFn(scopetochild,linkNode.childNodes,boundTranscludeFn);

4) postLink

// POSTLINKING
for(i = postLinkFns.length - 1; i >= 0; i--) {
  try {
    linkFn = postLinkFns[i];
    linkFn(linkFn.isolateScope ? isolateScope : scope,startingTag($element));
  }
}

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

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

4. transclude

4.1 transclude的定义配置

先回忆下transclude配置

{
  transclude: true // or 'element'
}
  • 当配置element时,被transclude的是整个元素
  • 当配置true是,被transclude的只是该元素的子元素

4.2 transclude主要源码

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

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

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

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

linkFn(linkFn.isolateScope ? isolateScope : scope,transcludeFn);

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

// boundTranscludeFn 是nodeLinkFn的参数
// function nodeLinkFn(childLinkFn,scope,linkNode,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,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,childBoundTranscludeFn);

// ...

// transcludeFn 就是第一if情况中的nodeLinkFn.transclude
// prevIoUsBoundTranscludeFn 就是parentBoundTranscludeFn
function createBoundTranscludeFn(scope,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,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,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,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,boundTranscludeFn);

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

由此传入publicLinkFn的parentBoundTranscludeFn

function publicLinkFn(scope,parentBoundTranscludeFn)

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

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

nodeLinkFn(childLinkFn,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,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,$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,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

angularjs源码笔记(1.1)--directive compile的更多相关文章

  1. HTML实现代码雨源码及效果示例

    这篇文章主要介绍了HTML实现代码雨源码及效果示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  2. 使用layui实现左侧菜单栏及动态操作tab项的方法

    这篇文章主要介绍了使用layui实现左侧菜单栏及动态操作tab项的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. 在iOS上绘制扭曲的文本

    使用iOS9及更高版本中提供的标准API,如何在绘制文本时实现扭曲效果?

  4. ios – 如果Element符合给定的协议,则扩展阵列以符合协议

    如果是这样,语法是什么?解决方法Swift4.2在Swift4.2中,我能够使用符合这样的协议的元素扩展数组:

  5. ios – 如何在swift中获取2数组的常见元素列表

    (双关语)编辑:,你可以这样做这个实现是丑陋的.

  6. 源码推荐:简化Swift编写的iOS动画,iOS Material Design库

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  7. swift皮筋弹动发射飞机ios源码

    这是一个款采用swift实现的皮筋弹动发射飞机游戏源码,游戏源码比较详细,大家可以研究学习一下吧。

  8. swift 写的app 源码,保存一下下

    http://www.topthink.com/topic/3345.htmlhttp://www.csdn.net/article/2015-01-09/2823502-swift-open-source-libs

  9. swift 源码网站 code4app

    http://code4app.com/ios/HTHorizontalSelectionList/54cb2c94933bf0883a8b4583http://123.th7.cn/code/DMPagerViewController_2522.html

  10. OpenStack Swift源码导读:业务整体架构和Proxy进程

    OpenStack的源码分析在网上已经非常多了,针对各个部分的解读亦是非常详尽。其中proxy是前端的业务接入进程。account、container和object目录分别是账户、容器和对象的业务处理逻辑进程。各个业务进程或模块之间的逻辑关系可以参考《OpenstackSwift简介》文中的架构图。在《OpenstackSwift简介》从理论上面介绍了具体的节点寻找过程。

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部