模板编译
源码地址:传送门
在数据劫持中,我们完成了Vue
中data
选项中数据的初始操作。这之后需要将html
字符串编译为render
函数,其核心逻辑如下:
有render
函数的情况下会直接使用传入的render
函数,而在没有render
函数的情况下,需要将template
编译为render
函数。其具体逻辑如下:
- 获取
template
字符串 - 将
template
字符串解析为ast
抽象语法树 - 将
ast
抽象语法树生成代码字符串 - 将字符串处理为
render
函数赋值给vm.$options.render
获取template
字符串
在进行template
解析之前,会进行一系列的条件处理,得到最终的template
,其处理逻辑如下:
![5cd2e544c9644607774bbf1544a0b974.png](https://img-blog.csdnimg.cn/img_convert/5cd2e544c9644607774bbf1544a0b974.png)
在src/init.js
中书写如下代码:
/**
* 将字符串处理为dom元素
* @param el
* @returns {Element|*}
*/
function query (el) {
if (typeof el === 'string') {
return document.querySelector(el);
}
return el;
}
function initMixin (Vue) {
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = options;
initState(vm);
const {
el } = options;
// el选项存在,会将el通过vm.$mount方法进行挂载
// el选项如果不存在,需要手动调用vm.$mount方法来进行组件的挂载
if (el) {
vm.$mount(el);
}
};
Vue.prototype.$mount = function (el) {
el = query(el);
const vm = this;
const options = vm.$options;
if (!options.render) {
// 有render函数,优先处理render函数
let template = options.template;
// 没有template,使用el.outerHTML作为template
if (!template && el) {
template = el.outerHTML;
}
options.render = compileToFunctions(template);
}
};
}
当我们得到最终的template
后,需要调用compileToFunctions
将template
转换为render
函数。在compileToFunctions
中就是模板编译的主要逻辑。
创建src/compiler/index.js
文件,其代码如下:
export function compileToFunctions (template) {
// 将html解析为ast语法树
const ast = parseHtml(template);
// 通过ast语法树生成代码字符串
const code = generate(ast);
// 将字符串转换为函数
return new Function(`with(this){return ${
code}}`);
}
解析html
当拿到对应的html
字符串后,需要通过正则来将其解析为ast
抽象语法树。简单来说就是将html
处理为一个树形结构,可以很好的表示每个节点的父子关系。
下面是一段html
,以及表示它的ast
:
<body>
<div id="app">
hh
<div id="aa" style="font-size: 18px;">hello {
{name}} world</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data () {
return {
name: 'zs',
};
},
});
</script>
</body>