聊聊Vue.js的template编译的问题
写在前面因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。 文章的原地址:https://github.com/answershuto/learnVue。 在学习过程中,为Vue加上了中文的注释https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以对其他想学习Vue源码的小伙伴有所帮助。 可能会有理解存在偏差的地方,欢迎提issue指出,共同学习,共同进步。 $mount首先看一下mount的代码 / istanbul ignore if /
if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( Do not mount Vue to <html> or <body> - mount to normal elements instead. ) return this } const options = this.$options /将template编译成render函数,这里会有render以及staticRenderFns两个返回,这是vue的编译时优化,static静态不需要在VNode更新时进行patch,优化性能/ / istanbul ignore if / 通过mount代码我们可以看到,在mount的过程中,如果render函数不存在(render函数存在会优先使用render)会将template进行compileToFunctions得到render以及staticRenderFns。譬如说手写组件时加入了template的情况都会在运行时进行编译。而render function在运行后会返回VNode节点,供页面的渲染以及在update的时候patch。接下来我们来看一下template是如何编译的。 一些基础首先,template会被编译成AST语法树,那么AST是什么? 在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。 AST会经过generate得到render函数,render的返回值是VNode,VNode是Vue的虚拟DOM节点,具体定义如下: ;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
functionalContext: Component | void; // only for functional component root nodes
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
/*Github:https://github.com/answershuto*/
constructor ( // DEPRECATED: alias for componentInstance for backwards compat. 关于VNode的一些细节,请参考VNode节点。 createCompilercreateCompiler用以创建编译器,返回值是compile以及compileToFunctions。compile是一个编译器,它会将传入的template转换成对应的AST树、render函数以及staticRenderFns函数。而compileToFunctions则是带缓存的编译器,同时staticRenderFns以及render函数会被转换成Funtion对象。 因为不同平台有一些不同的options,所以createCompiler会根据平台区分传入一个baSEOptions,会与compile本身传入的options合并得到最终的finalOptions。 compileToFunctions首先还是贴一下compileToFunctions的代码。 / istanbul ignore if /
if (process.env.NODE_ENV !== 'production') { // detect possible CSP restriction try { new Function('return 1') } catch (e) { if (e.toString().match(/unsafe-eval|CSP/)) { warn( 'It seems you are using the standalone build of Vue.js in an ' + 'environment with Content Security Policy that prohibits unsafe-eval. ' + 'The template compiler cannot work in this environment. Consider ' + 'relaxing the policy to allow unsafe-eval or pre-compiling your ' + 'templates into render functions.' ) } } } /Github:https://github.com/answershuto/ // check cache /有缓存的时候直接取出缓存中的结果即可/ const key = options.delimiters ? String(options.delimiters) + template : template if (functionCompileCache[key]) { return functionCompileCache[key] } // compile // check compilation errors/tips // turn code into functions // check function generation errors. /存放在缓存中,以免每次都重新编译/ 我们可以发现,在闭包中,会有一个functionCompileCache对象作为缓存器。 在进入compileToFunctions以后,会先检查缓存中是否有已经编译好的结果,如果有结果则直接从缓存中读取。这样做防止每次同样的模板都要进行重复的编译工作。 在compileToFunctions的末尾会将编译结果进行缓存 compile {
(tip ? tips : errors).push(msg)
}
/做下面这些merge的目的因为不同平台可以提供自己本身平台的一个baSEOptions,内部封装了平台自己的实现,然后把共同的部分抽离开来放在这层compiler中,所以在这里需要merge一下/ /基础模板编译,得到编译结果/ compile主要做了两件事,一件是合并option(前面说的将平台自有的option与传入的option进行合并),另一件是baseCompile,进行模板template的编译。 来看一下baseCompile baseCompilebaseCompile首先会将模板template进行parse得到一个AST语法树,再通过optimize做一些优化,最后通过generate得到render以及staticRenderFns。 parseparse的源码可以参见https://github.com/answershuto/learnVue/blob/master/vue-src/compiler/parser/index.js#L53。 parse会用正则等方式解析template模板中的指令、class、style等数据,形成AST语法树。 optimizeoptimize的主要作用是标记static静态节点,这是Vue在编译过程中的一处优化,后面当update更新界面时,会有一个patch的过程,diff算法会直接跳过静态节点,从而减少了比较的过程,优化了patch的性能。 generategenerate是将AST语法树转化成render funtion字符串的过程,得到结果是render的字符串以及staticRenderFns字符串。 至此,我们的template模板已经被转化成了我们所需的AST语法树、render function字符串以及staticRenderFns字符串。 举个例子来看一下这段代码的编译结果 {{text}}
hello world
{{item.name}}
{{item.value}} {{index}} --- 转化后得到AST树,如下图: 我们可以看到最外层的div是这颗AST树的根节点,节点上有许多数据代表这个节点的形态,比如static表示是否是静态节点,staticClass表示静态class属性(非bind:class)。children代表该节点的子节点,可以看到children是一个长度为4的数组,里面包含的是该节点下的四个div子节点。children里面的节点与父节点的结构类似,层层往下形成一棵AST语法树。 再来看看由AST得到的render函数 _c,_v,_s,_q看了render function字符串,发现有大量的_c,_v,_s,_q,这些函数究竟是什么? 带着问题,我们来看一下core/instance/render。 /创建VNode节点/
vm._c = (a,b,c,d) => createElement(vm,a,d,false) 通过这些函数,render函数最后会返回一个VNode节点,在_update的时候,经过patch与之前的VNode节点进行比较,得出差异后将这些差异渲染到真实的DOM上。 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |