Angularjs理解二

2023-10-31

1.dom加载完毕,找寻ng-app,先从上到下找相关的指令,然后分两阶段执行。先找到所有的指令,完成编译,得到一个个链接函数,最后在链接到一个个controller上。(还是边编译边链接??)
先执行:

injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',  
  function(scope, element, compile, injector, animate) {
    scope.$apply(function() {
      element.data('$injector', injector);
      compile(element)(scope);
    });
  }
]);

关键就是编译完成,得到连接函数,然后链接。

function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {  
  // 对于文本节点,使用<span>包装
  forEach($compileNodes, function(node, index) {
    if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
       $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
    }
  });

  // 调用compileNodes编译当前节点,返回链接函数
  var compositeLinkFn = 
        compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext);

  // 添加scope class
  compile.$$addScopeClass($compileNodes);
  var namespace = null;
  return function publicLinkFn(scope, cloneConnectFn, options) {
    // 后面再看
  };   

其中有个技巧:
forEach,查看
没有,[]与forEach,可以使用apply、call进行借用。但是ie不支持,chrome、ff支持。

if (!Array.prototype.forEach) {  
    Array.prototype.forEach = function(callback, thisArg) {  
        var T, k;  
        if (this == null) {  
            throw new TypeError(" this is null or not defined");  
        }  
        var O = Object(this);  
        var len = O.length >>> 0; // Hack to convert O.length to a UInt32  
        if ({}.toString.call(callback) != "[object Function]") {  
            throw new TypeError(callback + " is not a function");  
        }  
        if (thisArg) {  
            T = thisArg;  
        }  
        k = 0;  
        while (k < len) {  
            var kValue;  
            if (k in O) {  
                kValue = O[k];  
                callback.call(T, kValue, k, O);  
            }  
            k++;  
        }  
    };  
}

.可在css、注释中使用。
.jquery.wrap,就用后方的内容,包裹前方的内容。

其中编译,搜集本节点和父节点所有的指令,然后应用,然后得到指令的链接函数,然后在放到一个数组中。

function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) {  
  var linkFns = [],
    attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound;
  // 遍历当前层的所有节点
  for (var i = 0; i < nodeList.length; i++) {
  attrs = new Attributes();
  // 收集每个节点上的所有指令
  directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, ignoreDirective);
  // 应用指令,返回链接函数
  nodeLinkFn = (directives.length)
      ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], [], previousCompileContext)
      : null;
  // 添加scope class
  if (nodeLinkFn && nodeLinkFn.scope) {
     compile.$$addScopeClass(attrs.$$element);
  }
  // 如果父亲节点没有链接函数或者已终止(terminal)
  // 或者没有孩子节点
  // 孩子节点的链接函数就为null
  // 否则递归编译孩子节点
  childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
          !(childNodes = nodeList[i].childNodes) ||
          !childNodes.length)
     ? null
     : compileNodes(childNodes,
       nodeLinkFn ? (
        (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
         && nodeLinkFn.transclude) : transcludeFn);
  // 将当前节点的链接函数和孩子节点的链接函数都插入到linkFns数组中
  if (nodeLinkFn || childLinkFn) {
     linkFns.push(i, nodeLinkFn, childLinkFn);
     linkFnFound = true;
     nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn;
  }
  previousCompileContext = null;
  }
  // 如果有链接函数返回闭包(compositeLinkFn能访问linkFns)
  return linkFnFound ? compositeLinkFn : null;
  function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
  // 代码略,后面再说
  }
}

编译节点的主要流程为两步,第一步通过 collectDirectives 搜集当前节点的指令,第二步调用 applyDirectivesToNode 来应用指令,最后调用 compileNodes 函数递归编译当前节点的子节点,最后把所有的函数添加到一个内部的 linkFns 数组中,这个数组将在最后链接的时候用到。

收集指令

Angular收集指令是根据节点类型来的,一共有三种类型:

1.element node:先根据tagName使用 addDirective 来添加指令,然后遍历节点的attrs使用 addAttrInterpolateDirective 和 addDirective 来添加指令,最后通过className使用 addDirective 来添加指令

2.text node:调用 addTextInterpolateDirective 方法来添加指令

3.comment node:匹配注释,使用 addDirective 添加指令

收集指令的过程主要用到了三个方法: addDirective 、 addAttrInterpolateDirective 和 addTextInterpolateDirective

首先来看 addDirective 方法:

function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName) {  
  // 是被忽略的指令,返回null
  if (name === ignoreDirective) return null;
  var match = null;
  // hasDirectives系统在初始化的时候添加的一个内健指令对象集合,$injector
  if (hasDirectives.hasOwnProperty(name)) {
     for (var directive, directives = $injector.get(name + Suffix),
          i = 0, ii = directives.length; i < ii; i++) {
       try {
         directive = directives[i];
         if ((maxPriority === undefined || maxPriority > directive.priority) &&
              directive.restrict.indexOf(location) != -1) {
           if (startAttrName) {
             directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
           }
           // 合法指令添加到tDirectives数组中
           tDirectives.push(directive);
           match = directive;
         }
      } catch (e) { $exceptionHandler(e); }
    }
  }
  return match;
}
如果是文本节点就创建内部指令,监听scope变化然后设置节点的值

function addTextInterpolateDirective(directives, text) {  
  var interpolateFn = $interpolate(text, true);
  if (interpolateFn) {
    directives.push({
      priority: 0,
      compile: function textInterpolateCompileFn(templateNode) {
        var templateNodeParent = templateNode.parent(),
            hasCompileParent = !!templateNodeParent.length;

        if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);

        return function textInterpolateLinkFn(scope, node) {
          var parent = node.parent();
          if (!hasCompileParent) compile.$$addBindingClass(parent);
          compile.$$addBindingInfo(parent, interpolateFn.expressions);
          scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
            node[0].nodeValue = value;
          });
        };
      }
    });
  }
}
像如下的文本节点就会自动添加上面的指令,自动添加一个监听,通过原生方法来修改节点的值

<body>  
    {{ feenan }}
</body>  
应用指令到当前节点

收集完所有指令之后,接下来就要调用 applyDirectivesToNode 方法来将指令应用到当前节点上了,这个方法将会生成用于链接阶段的link函数。

这个函数比较复杂,我们拆开来看,其主体如下:

// 遍历当前节点的每个指令
for (var i = 0, ii = directives.length; i < ii; i++) {  
  // 1.判断scope类型
  // 2.判断是否需要有controller
  // 3.transclude的处理
  // 4.template的处理
  // 5.templateurl的处理
  // 6.compile处理
  // 7.terminal处理
}
// return 收集到的信息-nodeLinkFn
下面逐步来看看编译的完整过程,首先说明一下:当前节点 $compileNode -即要编译的节点

1.判断scope类型

if (directiveValue = directive.scope) {  
  // 跳过需要异步处理的指令,模板加载完成之后再处理
  if (!directive.templateUrl) {
    // scope属性为对象,例如{},则需要创建独立作用域
    if (isObject(directiveValue)) {
      newIsolateScopeDirective = directive;
    }
  }
  newScopeDirective = newScopeDirective || directive;
}
2.判断是否需要controller

// 同样,跳过要异步处理的指令,模板加载完成之后再处理
if (!directive.templateUrl && directive.controller) {  
  directiveValue = directive.controller;
  // 收集当前节点上所用要创建的controller
  controllerDirectives = controllerDirectives || createMap();
  controllerDirectives[directiveName] = directive;
}
3.transclude的处理

if (directiveValue = directive.transclude) {  
  hasTranscludeDirective = true;
  // element
  if (directiveValue == 'element') {
    hasElementTranscludeDirective = true;
    terminalPriority = directive.priority;
    $template = $compileNode;
    // 删除当前节点,替换为注释
    $compileNode = templateAttrs.$$element =
        jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' '));
    compileNode = $compileNode[0];
    replaceWith(jqCollection, sliceArgs($template), compileNode);

    // 编译当前节点
    childTranscludeFn = compile($template, transcludeFn, terminalPriority,  replaceDirective && replaceDirective.name, { nonTlbTranscludeDirective: nonTlbTranscludeDirective});
  } else { // true
    // 复制当前节点内容
    $template = jqLite(jqLiteClone(compileNode)).contents();
    // 清空当前节点
    $compileNode.empty();
    // 编译复制的内容
    childTranscludeFn = compile($template, transcludeFn);
  }
}
4.template的处理

if (directive.template) {  
  hasTemplate = true;
  templateDirective = directive;

  // 如果template为函数,则函数返回值为模板内容
  // 否则就是字符串,那么字符串就是模板内容
  directiveValue = (isFunction(directive.template))
      ? directive.template($compileNode, templateAttrs)
      : directive.template;

  directiveValue = denormalizeTemplate(directiveValue);

  // 如果replace为true
  if (directive.replace) {
    replaceDirective = directive;
    if (jqLiteIsTextNode(directiveValue)) {
      $template = [];
    } else {
      $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
    }
    // 模板的第一个节点
    compileNode = $template[0];

    if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
      // 没有要编译的内容或不是有效节点,报错
    }

    // 1.模板的第一个节点(compileNode)替换当前节点($compileNode)
    replaceWith(jqCollection, $compileNode, compileNode);

    var newTemplateAttrs = {$attr: {}};

    // 2.收集模板的第一个节点(compileNode)的所有指令
    var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
    // 3.当前节点($compileNode)剩余未编译的指令
    var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));

    if (newIsolateScopeDirective) {
      markDirectivesAsIsolate(templateDirectives);
    }
    // 4.$compileNode与compileNode的指令合并
    directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
    // 5.将$compileNode与compileNode的属性合并
    mergeTemplateAttributes(templateAttrs, newTemplateAttrs);

    ii = directives.length;
  } else {
    // replace为false,直接将模板内容插入当前节点即可
    $compileNode.html(directiveValue);
  }
}
5.templateurl的处理

if (directive.templateUrl) {  
  hasTemplate = true;
  templateDirective = directive;

  if (directive.replace) {
    replaceDirective = directive;
  }

  // 和template的处理类似,只是在获取到模板之前,这些节点编译挂起
  // 等获取到模板内容之后再继续编译
  nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
      templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
        controllerDirectives: controllerDirectives,
        newIsolateScopeDirective: newIsolateScopeDirective,
        templateDirective: templateDirective,
        nonTlbTranscludeDirective: nonTlbTranscludeDirective
      });
  ii = directives.length;
}
6.compile处理

// 同样,跳过需要异步处理的指令
if (!directive.templateUrl && directive.compile) {  
  try {
    // 使用指令的compile函数编译指令
    linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);

    // 如果返回的是函数,则该函数为post-link函数
    // 否则返回为对象,pre和post属性分别对应指令的pre-link和post-link函数
    if (isFunction(linkFn)) {
      addLinkFns(null, linkFn, attrStart, attrEnd);
    } else if (linkFn) {
      addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd);
    }
  } catch (e) {
    $exceptionHandler(e, startingTag($compileNode));
  }
}
7.terminal处理

// 有终止属性的指令,优先级小于当前指令的不会被编译
if (directive.terminal) {  
  nodeLinkFn.terminal = true;
  terminalPriority = Math.max(terminalPriority, directive.priority);
}
编译阶段收集了足够的信息,最后返回 nodeLinkFn 函数及其属性,在链接过程中就可以使用了:

nodeLinkFn = {  
  scope = newScopeDirective && newScopeDirective.scope === true
  transcludeOnThisElement = hasTranscludeDirective
  elementTranscludeOnThisElement = hasElementTranscludeDirective
  templateOnThisElement = hasTemplate
  transclude = childTranscludeFn
}
publicLinkFn(scope)

这里传进来的 scope 是 $rootScope ,这个方法主要是执行所有的链接函数,创建scope,添加监听函数:

function publicLinkFn(scope, cloneConnectFn, options) {  
  // 略去一大推
  if (cloneConnectFn) cloneConnectFn($linkNode, scope);
  if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
  return $linkNode;
};
这个函数实际调用的是 compositeLinkFn 函数,而 compositeLinkFn 函数是 compileNodes 函数的返回值,实际上它返回了一个闭包。 compositeLinkFn 函数操作的是 linkFns , linkFns 包含两部分:当前节点的链接函数 nodeLinkFn 和子节点的链接函数 childLinkFn ,而 childLinkFn 本身也是一个 compositeLinkFn (在子节点上递归调用 compileNodes 的返回结果),所以实际的链接过程就是递归调用 nodelinkFn 函数:

// 1.创建独立scope
if (newIsolateScopeDirective) {  
   isolateScope = scope.$new(true);
}
// 2.创建控制器
if (controllerDirectives) {  
   elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);

  for (i in elementControllers) {
    controller = elementControllers[i];
    var controllerResult = controller();
    // 略
  }
}

// 3.pre-link
for (i = 0, ii = preLinkFns.length; i < ii; i++) {  
  linkFn = preLinkFns[i];
  invokeLinkFn(linkFn,
    linkFn.isolateScope ? isolateScope : scope,
    $element,
    attrs,
    linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
    transcludeFn
  );
}

// 4.递归执行子节点的链接函数
var scopeToChild = scope;  
if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) {  
   scopeToChild = isolateScope;
}
childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);

// 5.post-link
for (i = postLinkFns.length - 1; i >= 0; i--) {  
  linkFn = postLinkFns[i];
  invokeLinkFn(linkFn,
      linkFn.isolateScope ? isolateScope : scope,
      $element,
      attrs,
      linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers),
      transcludeFn
  );
}
执行流程为 preLinkFns -> childLinkFn -> postLinkFns ,这些信息都已经在第一步编译的时候收集好了,链接的时候使用即可。所以pre-link的执行顺序和文档节点顺序相同,而post-link是在递归回溯的过程中执行的,因此正好和文档节点顺序相反,后面会对编译连接过程给出一个小例子。

这里补充说一下如何获取节点上的所有控制器,看代码:

function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {  
  var elementControllers = createMap();
  for (var controllerKey in controllerDirectives) {
    var directive = controllerDirectives[controllerKey];
    var locals = {
      $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
      $element: $element,
      $attrs: attrs,
      $transclude: transcludeFn
    };

    var controller = directive.controller;
    // 特殊处理ng-controller
    if (controller == '@') {
      controller = attrs[directive.name];
    }

    // 生成控制器实例,实际是返回已经注入依赖的工厂函数
    var controllerInstance = $controller(controller, locals, true, directive.controllerAs);

    // 含有 transclude 指令的元素是注释
    // jQuery不支持在注释节点设置data
    // 因此,暂时将controller设置在local hash中
    // 当 transclude 完成后,生成真正的节点之后,再将controller设置到data中
    elementControllers[directive.name] = controllerInstance;
    if (!hasElementTranscludeDirective) {
      $element.data('$' + directive.name + 'Controller', controllerInstance.instance);
    }
  }
  return elementControllers;
}

参考文献http://www.tuicool.com/articles/3QjyMvm

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Angularjs理解二 的相关文章

随机推荐

  • web项目时Spring监听器配置

    问题 每次使用ClassPathXmlApplicationContext 和getBean 方法时 都会加载spring配置文件 影响性能 解决方案 1 在服务器启动的时候 创建对象加载配置文件 2 底层使用监听器 listener 和S
  • ISE中FIFO IP核的Standard FIFO和First-word-Fall-Through模式的仿真比较

    ISE下的FIFO IP核有Standard FIFO和First word Fall Through两种模式 相对于标准模式FWFT First word Fall Through 可以不需要读命令 自动的将最新数据放在dout上 接下来
  • Qt中的串口编程之一

    QtSerialPort 简介 QtSerialPort模块是Qt5库的附加部分 为硬件和虚拟的串口提供了统一的接口 注意 该模块也增加了对Qt4的支持 串口由于其简单和可靠 目前在像嵌入式系统 机器人等工业中依旧用得很多 使用QtSeri
  • QFrame类使用总结

    QFrame与QWidget的区别 QFrame是基本控件的基类 QWidget是QFrame基类 关系如下 QPushButton QLabel gt QFrame gt QWidget 我们经常会从QFrame或者QWidget继承然后
  • 手机把网页保存为html,怎么保存整个网页

    手机评站网今天精心准备的是 怎么保存整个网页 下面是详解 如何另存整个网页 如何另存整个网页 如何另存整个网页 1 在手机桌面中找到手机百度 点击打开手机百度 如下图所示 2 在手机百度中找到自己想要另存为的网页 点击进入该网页如下图所示
  • Visual Studio运行程序执行太快,看不到运行屏幕的结果,设置项目属性解决。

    一 右击项目 找到属性 二 找到链接器 三 链接器中找到子系统 子系统 选择控制台 SUBSYSTEM CONSOLE 应用 确定即可 四 也可以补充getchar 可以利用从键盘获取一个字符 来显示调试窗口
  • C++ 二叉树序列化与反序列化

    本人微信公众号 CPP进阶之旅 如果觉得这篇文章对您有帮助 欢迎关注 CPP进阶之旅 学习更多技术干货 C 二叉树序列化与反序列化 1 题目要求 2 题目说明 3 核心问题 4 解题思路 5 代码实现 6 问题扩展 7 重要说明 1 题目要
  • 从源码出发浅析 Android TV 的焦点移动原理(下篇)

    转自 https cloud tencent com developer article 1006297 2 2 findNextFocus 如果开发者没有指定nextFocusId 则用findNextFocus找指定方向上最近的视图看一
  • 方差、协方差、期望、相关系数等概念集合

    首先说明一下 本文是本人在复习方差等相关知识的过程中 通过网络上的相关讲解 进行个人总结后得到的 并非个人原创 在此发布只是为了作为一个学习记录与大家分享 1 期望 试验中可能出现的值及其概率的乘积 即是数学期望 1 离散型 离散型随机变量
  • git add 命令详解

    1 前言 2 git add 基本操作 3 git add 命令参数 4 git add 背后做了什么 1 前言 众所周知 git 中有工作区 暂存区 版本库三大组成部分 工作区 电脑中能看到的目录 也就是写代码的地方 暂存区 英文叫 st
  • vue3中通过自定义指令,实现点击空白处触发事件,点击非自身dom触发事件

    我们经常在开发过程中 会遇到这些问题 怎么实现点击空白处关闭指定盒子 怎么实现点击空白处收起下拉框 即 怎么触发点击空白处事件 怎么触发 点击非自身dom而触发的事件 在vue3当中 使用自定义指令解决这个问题 在utils directi
  • 开启network-manager.service

    ubuntu20 04 本身系统会默认开机自动连接网络服务 但是我之前自己设置关闭了 所以现在要手动打开使用一下命令 先进入root xxz sudo systemctl start network manager service 回车执行
  • 《一》HI3518E视频处理基础知识----- 系统控制mpp

    目录 一 MPP的概述 1 视频方面 2 音频方面 3 MPP所处层次框架图 二 mpp处理平台架构 三 视频缓存池 1 视频缓冲池 VB 2 要点 3 相关的数据结构和API 1 VB CONF S 2 HI MPI VB SetConf
  • 家谱(特殊的层级人物关系)数据结构与自动排版算法的一种实现

    github源代码 家谱海本地私有版 https github com fengchangfight familytreesea 出处 http www fengchang cc post 24 家谱的数据结构并不复杂 逻辑上可以抽象成一种
  • BES2300x笔记(28) -- 左右耳同时按下的骚操作

    哈喽大家好 这是该系列博文的第二十八篇 篇 lt lt 系列博文索引 快速通道 gt gt 一 前言 市面上的TWS耳机 一般中高端耳机都会有触摸按键和入耳检测功能 使用触摸按键更方便外观和防水处理 但同时也限制了UI交互方式 有限的交互方
  • eclipse快速打开和闭合函数方法代码块的快捷方式

    ctrl shift 小键盘 收起和ctrl shift 小键盘 闭合
  • 基于深度学习的变化检测算法实现

    我是研究生期间研究主要研究SAR影像的变化检测 这是一段简单的基于深度学习的变化检测方法 以CNN实现 首先说下基于深度学习的变化检测任务的思路 制作训练样本 gt 训练模型 gt 使用训练的模型遍历图片中每个像元得出结果 1 筛选训练样本
  • spring.ftl

    lt ftl strip whitespace true gt lt spring ftl This file consists of a collection of FreeMarker macros aimed at easing so
  • css中垂直对齐常用的几种方法

    一 行高 line height 法 如果要垂直居中的只有一行或几个文字 那它的制作最为简单 只要让文字的行高和容器的高度相同即可 比如 p height 30px line height 30px width 100px overflow
  • Angularjs理解二

    1 dom加载完毕 找寻ng app 先从上到下找相关的指令 然后分两阶段执行 先找到所有的指令 完成编译 得到一个个链接函数 最后在链接到一个个controller上 还是边编译边链接 先执行 injector invoke rootSc