标签:语法 依赖 one eval jquer VID 直接 ast 显示
本文分析angular 1.0从初始化开始到编译网页更新页面的源代码过程以及一些重要细节。
测试项目例子:
<html ng-app=‘myapp‘ >
<body ng-controller="myController" >
<tip title="title"></tip>
</body>
controller和指令用angular.module的方法定义。
angular 1.0 以指令为中心,directive指令标签就是组件,有template,而属性指令主要是修改元素属性,angular 2.0改为以组件为中心设计。
angular入口初始化程序:
function angularInit(element, bootstrap) {
bootstrap(appElement, module ? [module] : []);
}
function bootstrap(element, modules) {
var doBootstrap = function() {
modules = modules || [];
modules.unshift([‘$provide‘, function($provide) {
$provide.value(‘$rootElement‘, element);
}]);
modules.unshift(‘ng‘);
var injector = createInjector(modules);
injector.invoke([‘$rootScope‘, ‘$rootElement‘, ‘$compile‘, ‘$injector‘, ‘$animate‘, //angular代码执行时已经创建内部对象,这些是对象的key(名字)
function(scope, element, compile, injector, animate) { // invoke([模块1,模块2,...,fn])就是调用执行fn,传递依赖模块(angular内部对象)
scope.$apply(function() { // 外套$apply执行方法,执行完方法之后扫描watcher重新获取所有watcher表达式的值进行必要的页面更新
element.data(‘$injector‘, injector);
compile(element)(scope); // 编译根元素,返回link函数,再执行link函数,更新页面的代码在每个指令表达式watcher的update方法中。
});
}]);
return injector;
};
return doBootstrap();
}
function createInternalInjector(cache, factory) {
function invoke(fn, self, locals){ //invoke就是变换参数和作用域调用函数, angular内部对象机制和依赖模块注入非常复杂,本文忽略
fn = fn[length];
return fn.apply(self, args);
function compile($compileNodes, transcludeFn, maxPrior //从根元素开始编译
var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, // 递归编译子节点
function compileNodes(nodeList, transcludeFn, $rootElement,
applyDirectivesToNode(directives,
function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
$compileNode.html(directiveValue); // 把指令的template<div>{{title}}</div>插入网页中的节点元素中
childLinkFn = compileNodes(childNodes,nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); // 有子节点则递归调用自身
return linkFnFound ? compositeLinkFn : null;
//每一次递归子节点时已经编译了子节点的指令,递归子节点层层返回到最上层compile代码位置时,返回的link函数已经包含
每一层递归时产生的link函数,也就是每一层递归时编译结果。最后只要再执行最终返回的link函数,传递根scope,把根scope保存
在根元素对象属性中,就完成了整个编译插入网页的过程,在每一次递归编译子节点时如果有指令会编译指令。
如果是路由组件,有template,是编译插入网页生效。
function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { //每一次递归返回的link函数,含每一次递归编译的结果数据
return function publicLinkFn(scope, cloneConnectFn, // compile返回的link函数,返回后会执行link函数传递scope
$linkNode.eq(i).data(‘$scope‘, scope); // node是元素jquery对象,把scope保存到元素对象的属性中
if (cloneConnectFn) cloneConnectFn($linkNode, scope);
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode); // link函数含层层递归编译结果数据
return $linkNode;
angular的link函数比较费解,它主要是关联元素和scope/controller instance,一般情况下是没用的,scope从rootscope开始就是一个树结构,从rootscope可以递归所有的子scope,
controller的方法中引用某一个已经创建存在的scope,scope按id区分和索引,都和元素没关系,数据层面与元素是没有直接关系的。
编译方法compile外套$apply,$apply会调用$digest:
$digest: function() {
//递归scope找watcher,watcher数据中没有scope,因此执行watcher的方法时要传递scope,执行watcher的方法时会变换作用域为scope
value = watch.get(current) // 获取表达式的值时要传递scope,表达式{{title}}的值是hello
watch.fn(value, ((last === initWatchVal) ? value : last), current); // 执行watch.fn之后网页显示hello,fn就是handler/update函数
代码中还涉及到defer和settimeout是延迟函数,实现异步调度,非功能性流程。
因此angular在初始化时编译网页时针对每个表达式建立了watcher,编译程序外套$apply,会调用$digest扫描执行watcher的update方法更新网页。
vue也是在初始化编译template时针对每个表达式建立watcher,为组件的data属性建立set/get方法,只要set数据操作,就会执行相应的watcher的update更新页面。
vue 2.0是针对组件建立watcher,初始化时编译根组件执行根组件watcher的update更新页面,set数据时执行相应的组件的watcher的update方法更新页面,组件
可能有子组件嵌套,那么从组件递归重新编译template产生vnode,再根据vnode更新页面。
angular是在数据操作之后执行$digest扫描执行watcher更新页面,vue是set触发执行watcher更新页面。
react是在初始化编译时递归编译子节点,然后把编译结果插入网页生效,当数据变化时,通过Connect组件的Listener或setState执行组件的render()方法重新编译组件
更新页面。
angular和vue都是用compile方法递归编译网页元素,这是它们的核心程序,react也是类似的,其mountComponent其实就是递归编译程序,因此所有的框架在基本原理方法方面其实都是用类似的设计方法,都是设计一个核心编译程序,递归编译所有的子节点。
在template和表达式的写法方面不太一样,react比较特殊,它用render()方法写template,用JSX语法,用babel编译解析,产生一个含层层嵌套的createElement()的函数,执行这个函数就产生一个根元素。
vue 2.0也是用类似的方法,编译template产生一个render方法代码,含层层嵌套的createElement方法,再执行render方法代码产生一个根元素Element。
标签:语法 依赖 one eval jquer VID 直接 ast 显示
原文地址:https://www.cnblogs.com/pzhu1/p/8989985.html