AngularJS 源码分析4
来源:互联网 发布:java模板引擎排行 编辑:程序博客网 时间:2024/04/28 03:44
angularjs之$compile
今天主要说说ng里的$compile
,这是一个非常关键的服务,页面上的双向绑定,各个监听基本上都是在这里执行的.
源码部分还是引用angular1.2.4,链接在这里下载
compile的源头
ng里最开始引用$compile
的地方就是把所有系统内建的指令添加到$CompileProvider
里,由于代码太长,只写些关键部分的
$provide.provider('$compile', $CompileProvider). directive({ a: htmlAnchorDirective, input: inputDirective, textarea: inputDirective, form: formDirective, script: scriptDirective, select: selectDirective, style: styleDirective, option: optionDirective, ngBind: ngBindDirective, ngBindHtml: ngBindHtmlDirective, ngBindTemplate: ngBindTemplateDirective, ngClass: ngClassDirective, ngClassEven: ngClassEvenDirective, ngClassOdd: ngClassOddDirective, ngCloak: ngCloakDirective, ngController: ngControllerDirective, ngForm: ngFormDirective, ngHide: ngHideDirective, ngIf: ngIfDirective, ngInclude: ngIncludeDirective, ngInit: ngInitDirective, ngNonBindable: ngNonBindableDirective, ngPluralize: ngPluralizeDirective, ngRepeat: ngRepeatDirective, ngShow: ngShowDirective, ngStyle: ngStyleDirective, ngSwitch: ngSwitchDirective, ngSwitchWhen: ngSwitchWhenDirective, ngSwitchDefault: ngSwitchDefaultDirective, ngOptions: ngOptionsDirective, ngTransclude: ngTranscludeDirective, ngModel: ngModelDirective, ngList: ngListDirective, ngChange: ngChangeDirective, required: requiredDirective, ngRequired: requiredDirective, ngValue: ngValueDirective }). directive({ ngInclude: ngIncludeFillContentDirective }). directive(ngAttributeAliasDirectives). directive(ngEventDirectives);
此处的directive
方法就是$CompileProvider
里的registerDirective
方法,主要就是把内建指令添加到内部的hasDirectives
对象内,以方便后面在全局查找指令的时候进行匹配.
compile的启动
启动的方法在这里,只摘取关键代码.
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', function(scope, element, compile, injector, animate) { scope.$apply(function() { element.data('$injector', injector); compile(element)(scope); }); }] );
上面的代码主要作用就是,初始化相关的依赖,然后执行全局编译,最后更新所有的$watch.
核心的代码就这一句
compile(element)(scope);
其实这里有两步
compile(element) 收集完整个页面内的指令,然后返回
publicLinkFn
函数执行publicLinkFn(scope) 此处的scope即为
$rootScope
先来说说第一步
compile(element)
compile
服务返回的是一个构造函数,名为compile
,代码在这里
function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective,previousCompileContext) { if (!($compileNodes instanceof jqLite)) { $compileNodes = jqLite($compileNodes); } forEach($compileNodes, function(node, index){ if (node.nodeType == 3 && node.nodeValue.match(/\S+/) ) { $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0]; } }); var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext); safeAddClass($compileNodes, 'ng-scope'); return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){ // 代码太长,省略 }; }
从上面的代码可以看出,如果要查找的节点是文本元素,则包装一个span
标签,然后执行compileNodes
,这个方法主要是收集指令.
function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective,previousCompileContext) { var linkFns = [], attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound; 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; if (nodeLinkFn && nodeLinkFn.scope) { safeAddClass(jqLite(nodeList[i]), 'ng-scope'); } childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !(childNodes = nodeList[i].childNodes) || !childNodes.length) ? null : compileNodes(childNodes, nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); linkFns.push(nodeLinkFn, childLinkFn); linkFnFound = linkFnFound || nodeLinkFn || childLinkFn; //use the previous context only for the first element in the virtual group previousCompileContext = null; } return linkFnFound ? compositeLinkFn : null; function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { // 代码省略 } }
上面的编译节点的主要流程就是,先通过collectDirectives
搜集当前节点的指令,然后找到了,则调用applyDirectivesToNode
来应用指令,然后查找当前节点的子节点是否有指令,这是一个递归,最后把所有的函数添加到一个内部的linkFns
数组中,这个将在最后链接的时候会用到.
先来看看collectDirectives
方法,这个方法代码比较长,就不贴了,直接说代码逻辑
ng收集指令的时候,首先根据节点类型
element_node 1 先根据tagName来添加指令,然后loop节点的attrs来添加指令,最后通过className来添加指令
text_node 3 假如是文本节点的话,则调用
addTextInterpolateDirective
方法来构建指令
function addTextInterpolateDirective(directives, text) { var interpolateFn = $interpolate(text, true); if (interpolateFn) { directives.push({ priority: 0, compile: valueFn(function textInterpolateLinkFn(scope, node) { var parent = node.parent(), bindings = parent.data('$binding') || []; bindings.push(interpolateFn); safeAddClass(parent.data('$binding', bindings), 'ng-binding'); scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { node[0].nodeValue = value; }); }) }); } }
像这样的文本节点就会自动构建上面的指令,自动添加一个监听,通过修改原生方法来修改节点的值
<body> {{ feenan }}</body>
- comment 8 注释的节点也能自动的添加指令
上面的三种情况的添加指令方法是addDirective
function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName,endAttrName) { if (name === ignoreDirective) return null; var match = null; 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.push(directive); match = directive; } } catch(e) { $exceptionHandler(e); } } } return match; }
这里就会用到这个对象hasDirectives
,这就是系统在初始化的时候添加的一个内健指令对象集合.
假如节点的名称在这个对象,则把指令添加到传递进来的tDirectives
数组内.返回当前指令.
搜集完指令之后,就要开始使用了,接下来调用applyDirectivesToNode
方法,这个方法将会生成最终链接时候调用的link函数
applyDirectivesToNode
会对directives
进行loop,依次检查指令的属性,这里以compile
属性来说,当检测到指令有compile
属性,则
if (directive.compile) { try { linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); if (isFunction(linkFn)) { addLinkFns(null, linkFn, attrStart, attrEnd); } else if (linkFn) { addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); } } catch (e) { $exceptionHandler(e, startingTag($compileNode)); } }
执行directive.compile
方法,返回一个linkFn
,然后调用addLinkFns
添加到内部数组中,这里是postLinkFns
数组,最终执行用户定义的linkFn
或者系统自带的,都会访问这个数组的内容
最后applyDirectivesToNode
返回的是一个内部函数nodeLinkFn
,这个就是调用用户定义指令函数的发起者.
当前节点指令处理完之后,然后开始查找子节点的指令,基本上跟父节点规则一样,最后返回compositeLinkFn
函数给compositeLinkFn
内部变量,这个下面会用到,最后整个compile
函数返回publicLinkFn
函数
到这里compile(element)
就执行完了,再来说说第二步,最终进行指令链接
publicLinkFn(scope)
首先scope
是根作用域,这个方法主要是执行所有的链接函数,添加监听函数.
function publicLinkFn(scope, cloneConnectFn, transcludeControllers){ assertArg(scope, 'scope'); var $linkNode = cloneConnectFn ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! : $compileNodes; forEach(transcludeControllers, function(instance, name) { $linkNode.data('$' + name + 'Controller', instance); }); // 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 || nodeType === 9 ) { $linkNode.eq(i).data('$scope', scope); } } if (cloneConnectFn) cloneConnectFn($linkNode, scope); if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode); return $linkNode;};
把当前作用域保存到元素的data里,然后调用第一步里的compositeLinkFn
函数,传递根作用域和根节点
这个会调用compileNodes
里的compositeLinkFn
方法,此时闭包属性linkFns
属性里保存了两个项nodeLinkFn, childLinkFn
,根节点的nodelinkFn
为空,childlinkFn
有值,它的值本身也是一个compositeLinkFn
函数,然后传递根节点的子节点进去,最终当nodelinkFn
有值的时候,会调用applyDirectivesToNode
内部的nodeLinkFn
方法,上面说了,这个调用所有链接函数的发起者.
nodeLinkFn
代码比较长,就不贴了,这里主要做了以下几件事
根据指令的scope属性来构建作用域信息
是否需要构建控制器,此时会调用控制器的初始化信息
执行prelinkfns,postlinkfns数组内的链接函数,这些都是在第一步收集好的
核心代码如下
// PRELINKINGfor(i = 0, ii = preLinkFns.length; i < ii; i++) { try { linkFn = preLinkFns[i]; linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); } catch (e) { $exceptionHandler(e, startingTag($element)); }}// RECURSION// We only pass the isolate scope, if the isolate directive has a template,// otherwise the child elements do not belong to the isolate directive.var scopeToChild = scope;if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { scopeToChild = isolateScope;}childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);// POSTLINKINGfor(i = postLinkFns.length - 1; i >= 0; i--) { try { linkFn = postLinkFns[i]; linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); } catch (e) { $exceptionHandler(e, startingTag($element)); }}
执行流程为preLinkFns -> childLinkFn -> postLinkFns
最终执行链接的函数在这里
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn);
这里就会执行用户自定义的指令内容,以及系统自带的指令内容,像上面文本节点对应的指令内容,像下面的这个
function textInterpolateLinkFn(scope, node) { var parent = node.parent(), bindings = parent.data('$binding') || []; bindings.push(interpolateFn); safeAddClass(parent.data('$binding', bindings), 'ng-binding'); scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { node[0].nodeValue = value; });}
指令内容里可以添加监听,写一些DOM操作的代码,都是可以的
总结
以上只是对编译服务的一些简单理解,有啥错误的希望大家指出来,一起进步,以后有空再分析下业务相关的Provider
.
欢迎转载,转载请注明作者和出处:feenan
- AngularJS 源码分析4
- AngularJS源码 nextUid()分析
- angularjs 源码分析1
- AngularJS 源码分析1
- AngularJS 源码分析2
- AngularJS 源码分析3
- AngularJS RootScope 源码分析
- AngularJS RootScope 源码分析
- angularjs源码分析之:angularjs执行流程
- angularjs源码分析之:angularjs执行流程
- Angularjs 源码分析-setupModuleLoader
- 与AngularJS的约会之事件循环+watchers源码分析
- angularjs源码阅读
- angularjs产品列表源码
- AngularJS PhoneCat代码分析
- AngularJS渲染性能分析
- AngularJS PhoneCat代码分析
- Leveldb源码分析--4
- AngularJS 源码分析1
- 错误记录——几个相似变量名混淆
- AngularJS 源码分析2
- android--登录--验证
- AngularJS 源码分析3
- AngularJS 源码分析4
- 最常用的15大Eclipse开发快捷键技巧
- Linux下修复“fatal error: jsoncpp/json/json.h: No such file or directory” 分类: C++ | 标签: Linux,jsonjson
- Servlet跳转
- 面向对象3
- 个人心记
- 2015-2016年第一学期第一周协会活动 二分搜索
- hadoopSSH无密码登录
- C++的Json解析库:jsoncpp和boost .