微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Vue AST源码解析第一篇

讲完了数据劫持原理和一堆初始化,现在是DOM相关的代码了。

上一节是从这个函数开始的:

rush:js;"> // Line-3924 Vue.prototype._init = function(options) { // 大量初始化 // ... // Go! if (vm.$options.el) { vm.$mount(vm.$options.el); } };

弄完data属性的数据绑定后,开始处理el属性,也就是挂载的DOM节点,这里的vm.$options.el也就是传进去的'#app'字符串。

一个值得注意的点是,源码中有2个$mount函数都是Vue$3的原型函数,其中一个标记了注释public mount method,在7531行,另外一个在9553行。打断点进入的是后面,因为定义的晚,覆盖了前面的函数

rush:js;"> // Line-7531 // public mount method Vue$3.prototype.$mount = function(el,hydrating) { el = el && inbrowser ? query(el) : undefined; return mountComponent(this,el,hydrating) };

// Line-9552
var mount = Vue$3.prototype.$mount;
Vue$3.prototype.$mount = function(
el,hydrating
) {
// ...很多代码
return mount.call(this,hydrating)
};

现在进入后面的$mount函数看看内部结构:

rush:js;"> // Line-9552 var mount = Vue$3.prototype.$mount; Vue$3.prototype.$mount = function(el,hydrating) { // 将el格式化为DOM节点 el = el && query(el); // 判断是否挂载到body或者html标签上 if (el === document.body || el === document.documentElement) { "development" !== 'production' && warn( "Do not mount Vue to or - mount to normal elements instead." ); return this }

var options = this.$options;
// 处理template/el 转换为渲染函数
if (!options.render) {
// ...非常多代码
}
return mount.call(this,hydrating)
};

代码前半段首先将el转换为DOM节点,并判断是否挂载到body或者html标签,看看简单的query函数

rush:js;"> // Line-4583 function query(el) { // 如果是字符串就调用querySelector if (typeof el === 'string') { var selected = document.querySelector(el); if (!selected) { "development" !== 'production' && warn( 'Cannot find element: ' + el ); // 找不到就返回一个div return document.createElement('div') } return selected } // 不是字符串就认传进来的是DOM节点 else { return el } }

函数比较简单,值得注意的几个点是,由于调用的是querySelector方法,所以可以传标签名、类名、C3新选择器等,都会返回查询到的第一个。当然,总是传一个ID或者确定的DOM节点才是正确用法

下面看接下来的代码

rush:js;"> // Line-9552 var mount = Vue$3.prototype.$mount; Vue$3.prototype.$mount = function(el,hydrating) { // ...el转换为DOM节点 // ... // 没有render属性 进入代码段 if (!options.render) { var template = options.template; // 没有template 跳 if (template) { if (typeof template === 'string') { if (template.charat(0) === '#') { template = idToTemplate(template); /* istanbul ignore if */ if ("development" !== 'production' && !template) { warn( ("Template element not found or is empty: " + (options.template)),this ); } } } else if (template.nodeType) { template = template.innerHTML; } else { { warn('invalid template option:' + template,this); } return this } } // 有el 获取字符串化的DOM树 else if (el) { template = getouterHTML(el); } if (template) { // ...小段代码 } } return mount.call(this,hydrating) };

由于没有template属性,会直接进入第二个判断条件,调用getouterHTML来初始化template变量,函数比较简单, 来看看:

rush:js;"> // Line-9623 function getouterHTML(el) { if (el.outerHTML) { return el.outerHTML } // 兼容IE中的SVG else { var container = document.createElement('div'); container.appendChild(el.cloneNode(true)); return container.innerHTML } }

简单来讲,就是调用outerHTML返回DOM树的字符串形式,看图就明白了:

下面看最后一段代码

rush:js;"> // Line-9552 var mount = Vue$3.prototype.$mount; Vue$3.prototype.$mount = function(el,hydrating) { // ...el转换为DOM节点 // ... // 没有render属性 进入代码段 if (!options.render) { // ...处理template // ... if (template) { // 编译开始 if ("development" !== 'production' && config.performance && mark) { mark('compile'); }
// 将DOM树字符串编译为<a href="https://www.jb51.cc/tag/hanshu/" target="_blank" class="keywords">函数</a>
var ref = compil<a href="https://www.jb51.cc/tag/eto/" target="_blank" class="keywords">eto</a>Functions(template,{
 shouldDecodeNewli<a href="https://www.jb51.cc/tag/nes/" target="_blank" class="keywords">nes</a>: shouldDecodeNewli<a href="https://www.jb51.cc/tag/nes/" target="_blank" class="keywords">nes</a>,delimiters: options.delimiters
},this);
// options<a href="https://www.jb51.cc/tag/tianjia/" target="_blank" class="keywords">添加</a><a href="https://www.jb51.cc/tag/shuxing/" target="_blank" class="keywords">属性</a>
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns;

// 编译结束
if ("development" !== 'production' && con<a href="https://www.jb51.cc/tag/fig/" target="_blank" class="keywords">fig</a>.performance && mark) {
 mark('compile end');
 measure(((this._name) + " compile"),'compile','compile end');
}

}
}
return mount.call(this,hydrating)
};

忽略2段dev模式下的提示代码,剩下的代码做了3件事,调用compiletoFunctions函数肢解DOM树字符串,将返回的对象属性添加到options上,再次调用mount函数

首先看一下compiletoFunctions函数,该函数接受3个参数,分别为字符串、配置对象、当前vue实例。

由于函数比较长,而且部分是错误判断,简化后如下:

rush:js;"> // Line-9326 function compiletoFunctions(template,options,vm) { // 获取配置参数 options = options || {};

// ...

var key = options.delimiters ?
String(options.delimiters) + template :
template;
// 检测缓存
if (functionCompileCache[key]) {
return functionCompileCache[key]
}

// 1
var compiled = compile(template,options);

// ...

// 2
var res = {};
var fnGenErrors = [];
res.render = makeFunction(compiled.render,fnGenErrors);
var l = compiled.staticRenderFns.length;
res.staticRenderFns = new Array(l);
for (var i = 0; i < l; i++) {
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i],fnGenErrors);
}

// ...

// 3
return (functionCompileCache[key] = res)
}

可以看到,这个函数流程可以分为4步,获取参数 => 调用compile函数进行编译 => 将得到的compiled转换为函数 => 返回并缓存。

第一节现在这样吧。一张图总结下:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐