Angularjs理解二
2016-06-16 09:47
591 查看
1.dom加载完毕,找寻ng-app,先从上到下找相关的指令,然后分两阶段执行。先找到所有的指令,完成编译,得到一个个链接函数,最后在链接到一个个controller上。(还是边编译边链接??)
先执行:
关键就是编译完成,得到连接函数,然后链接。
其中有个技巧:
forEach,查看
没有,[]与forEach,可以使用apply、call进行借用。但是ie不支持,chrome、ff支持。
.可在css、注释中使用。
.jquery.wrap,就用后方的内容,包裹前方的内容。
其中编译,搜集本节点和父节点所有的指令,然后应用,然后得到指令的链接函数,然后在放到一个数组中。
编译节点的主要流程为两步,第一步通过 collectDirectives 搜集当前节点的指令,第二步调用 applyDirectivesToNode 来应用指令,最后调用 compileNodes 函数递归编译当前节点的子节点,最后把所有的函数添加到一个内部的 linkFns 数组中,这个数组将在最后链接的时候用到。
参考文献:http://www.tuicool.com/articles/3QjyMvm
先执行:
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
相关文章推荐
- AngularJS基础教程之简单介绍
- AngularJS中处理多个promise的方式
- AngularJS入门(用ng-repeat指令实现循环输出
- angularJS 中$attrs方法使用指南
- AngularJS实现textarea记录只能输入规定数量的字符并显示
- 深入浅析AngularJS和DataModel
- 简述AngularJS相关的一些编程思想
- 不能不知道的10个angularjs英文学习网站
- AngularJS中的$watch(),$digest()和$apply()区分
- Angularjs中的事件广播 ―全面解析$broadcast,$emit,$on
- AngularJS的一些基本样式初窥
- AngularJS 如何在控制台进行错误调试
- 简单讲解AngularJS的Routing路由的定义与使用
- 创建你的第一个AngularJS应用的方法
- Angularjs过滤器使用详解
- 详解AngularJS中的作用域
- Angular发布1.5正式版,专注于向Angular 2的过渡
- 简介AngularJS的视图功能应用
- AngularJS语法详解
- AngularJS 2.0新特性有哪些