Vue模板编译
Vue生命周期中,在初始化阶段各项工作做完之后调用了vm.$mount方法,该方法的调用标志着初始化阶段的结束和进入下一个阶段,从官方文档给出的生命周期流程图中可以看到,下一个阶段就进入了模板编译阶段(created和beforeMounted之间的阶段),该阶段所做的主要工作是获取到用户传入的模板内容并将其编译成渲染函数生成模板字符串。然后将这些模板字符串转换成内存中的DOM。
一、Vue 编译原理这块的整体逻辑主要分三个部分:
1.将模板字符串转换成element AST(抽象语法树)(解析器parser)
2.对AST进行静态节点标记,主要用来做虚拟dom的渲染优化(优化器optimizer)
3.使用element AST 生成render函数代码字符串(代码生成器code generator )
伪代码
// main.js文件
new Vue({el: '#app',templete: `<div>hello world</div>`, //可选render: h => h(App), //渲染函数
}).$mount('#app') //挂载函数Vue.prototype.__init = function (options) {const vm = this;vm.$options = options;initState(vm);if (vm.$options.el) {//$mount 挂载函数vm.$mount(vm.$options.el)}
}Vue.prototype.$mount = function (el) {const vm = this, options = vm.$options;el = document.querySelector(el);vm.$el = el;if (!options.render) {let templete = options.templete; //是否有templete选项if (!templete && el) {templete = el.outerHTML}//把这个模板变成render函数const render = compileToRenderFunction(templete); //把HTML转化成SAT树options.render = render;}}
解析器AST在线生成
<div class='container' id='root'><p class='name'>{{name}}</p>
</div>
上面一个简单 的模版转换成element AST树形结构后是这样的
{tag: "div"type: 1,staticRoot: false,static: false,plain: true,parent: undefined,attrsList: [{name:'class',value:'container'},{name:'id':value:'root'}],attrsMap: {},children: [{tag: "p"type: 1,staticRoot: false,static: false,plain: true,parent: {tag: "div", ...},attrsList: [{name:'class', value:'name'}],attrsMap: {},children: [{type: 2,text: "{{name}}",static: false,expression: "_s(name)"}]}]
}
我们可以看到上面的dom被解析成了解析器,它的原理主要是两部分内容,一部分是截取字符串,一部分是对截取的字符串做解析。
优化器
优化器的目的就是找出那些静态节点并打上标记,而静态节点指的是DOM不需要发生变化的节点,也就是里面都是静态标签和静态文本。
标记静态节点有两个好处:
- 一、每次重新渲染的时候不需要为静态节点创建新节点,也就是静态节点的解析器不需要重新创建
- 二、在虚拟 DOM中patching的过程可以被跳过
优化器的实现原理主要分两步:
- 一、用递归的方式将静态节点添加static属性,用来标识是不是静态节点
- 二、标记所有静态根节点(子节点全是静态节点就是静态根节点)
代码生成器
代码生成器的作用是使用element ASTs生成render函数代码字符串(带有__c, v, _s)
使用本文开头举的例子中的模版生成后的AST来生成render后是这样的:
{render: with(this){return _c('div',[_c('p',[_v(_s(name))])])
}
}
生成后的代码字符串中看到了有几个函数调用_c、_v、_s。
_c对应的是createElement,它的作用是创建一个元素。
1.第一个参数是一个HTML标签名
2.第二个参数是元素上使用的属性所对应的数据对象,可选项
3.第三个参数是children
_v的意思是创建一个文本节点。
_s是返回参数中的字符串。
代码生成器的总体逻辑其实就是使用element ASTs去递归,然后拼出这样的_c(‘div’,[_c(‘p’,[_v(_s(name))])]) 字符串。
问题:在模板编译的阶段,是不是就会对每个组件所使用的data或者computed值进行访问,从而创建新的Watcher进行订阅,然后对应的属性的dep就会收集这些watcher,从而实现更新的?
答:不是的,模板编译只是会把模板编译成渲染函数,只有在渲染函数被执行的时候才会对数据进行访问,而渲染函数是在watche实例中执行的,所以渲染函数中所使用到的所有数据,都会被同一个Watcher监听,当这些状态发生变化时,会通知这个Watcher,这个Watcher会触发VirtualDOM对组件进行渲染。一个组件的模板会被编译成一个渲染函数。每个组件有一个Watcher用来监听模板中所使用到的数据、当这些数据发生变化时,通过VirtualDOM进行更新组件的视图