一,前言
上篇,主要介绍了 vue 数据渲染核心流程,涉及以下几个点:
初次渲染时
- template 模板被编译为 ast 语法树;
- 通过 ast 语法树生成 render 函数;
- 通过 render 函数返回 vnode 虚拟节点;
- 使用 vnode 虚拟节点生成真实 dom 并进行渲染;
视图更新时
- 调用 render 函数生成新的 vnode 虚拟节点;
- 通过 diff 算法对新老 vnode 虚拟节点进行对比;
- 根据虚拟节点比对结果,更新真实 dom;
本篇,生成 ast 语法树-流程说明
二,Vue 提供的使用方式
1,三种模板写法及优先级
<body>
<!-- 第一种 -->
<div id=app>{{message}}</div>
<script src="./vue.js"></script>
<script>
debugger;
let vm = new Vue({
el: '#app',
data() {
},
// 第二种
template:'',
// 第三种
render(){}
});
</script>
</body>
三种写法的优先级【由高到低】:
- 使用 render
- 使用 template
- 使用元素中的内容;即使用
<div id=app>{{message}}</div>
中的{{message}}
2,两种数据挂载方式
在 Vue2.x 中,提供了两种挂载方式:
let vm = new Vue({
// el: '#app', // 挂载方式一
data() {
},
}).$mount('#app'); // 挂载方式 2
当挂载点 vm.$options.el 存在,或直接调用了 Vue 的原型方法 $mount 时,
就会通过Vue 上的原型方法 $mount 对数据进行挂载操作
三,Vue 的原型方法 $mount
<body>
<div id=app>{{message}}</div>
<script>
let vm = new Vue({
el: '#app',
data() {...},
});
</script>
</body>
当 vm.$options.el 存在,或直接调用 Vue 的原型方法 $mount 时,就会对数据进行挂载操作;
所以,设置 vm. o p t i o n s . e l 或 . options.el 或 . options.el或.mount,内部都是调用 Vue 原型方法 $mount 进行处理的;
在 $mount 中,拿到 el 挂载点指向的真实 dom 元素,并使用新内容将它替换掉
// src/init.js#initMixin
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = options;
initState(vm);
if (vm.$options.el) {
// 将数据挂载到页面上(此时数据已被观测)
vm.$mount(vm.$options.el)
}
}
// 支持 new Vue({el}) 和 new Vue().$mount 两种情况
Vue.prototype.$mount = function (el) {
const vm = this;
el = document.querySelector(el); // 获取真实的元素
vm.$el = el; // vm.$el 为当前页面上的真实元素
}
}
如何拿到"id = app"对应的元素,outerHTML or innerHTML?
- outerHTML:
<div id=app>{{message}}</div>
- innerHTML:
{{message}}
这里,由于需要使用的新内容替换掉老的内容,
所以,需要使用outerHTML拿到全部内容
再结合不同模板写法的优先级逻辑:
// src/init.js
Vue.prototype.$mount = function (el) {
const vm = this;
const opts = vm.$options;
el = document.querySelector(el);
vm.$el = el;
// 如果没有 render, 找 template
if (!opts.render) {
// 如果没有 template, 采用元素中的内容
const template = opts.template;
if (!template) {
// 拿到整个元素标签
console.log(el.outerHTML);
}
}
}
运行查看结果:
四,将模板编译为 ast 语法树
1,compileToFunction
在 vue 中,编译阶段的最终结果是输出 render 函数:
- parserHTML:将模板内容编译为 ast 语法树;
- generate:再根据 ast 语法树生成为 render 函数;
// src/compiler/index.js
export function compileToFunction(template) {
// 1,将模板变成 AST 语法树
let ast = parserHTML(template);
// 2,使用 AST 生成 render 函数
let code = generate(ast);
}
function parserHTML(template) {
console.log("parserHTML-template : " + template)
}
function generate(ast) {
console.log("parserHTML-ast : " + ast)
}
在 Vue 中,compileToFunction 方法是 Vue 编译的入口,
完成了以上两个操作,最终将模板编译成为 render 函数;
2,parserHTML
parserHTML 方法:将 HTML 模板编译成为 ast 语法树
注意:这里的 template 指的是 <template>
标签内部的内容,不包含 <template>
标签
compileToFunction(template) 方法,对 html 模板进行处理,需要传入 html 模板:
在 Vue 初始化时:
- 如果 options 选项中设置了 template,将优先使用 template 内容作为模板:
<div id="app"></div>
- 如果 options 选项没有设置 template,将采用元素内容作为 Html 模板
代码实现
Vue.prototype.$mount = function (el) {
const vm = this;
const opts = vm.$options;
el = document.querySelector(el); // 获取真实的元素
vm.$el = el; // vm.$el 表示当前页面上的真实元素
// 如果没有 render, 看 template
if (!opts.render) {
// 如果没有 template, 采用元素内容
let template = opts.template;
console.log("template = " + template)
if (!template) {
// 拿到整个元素标签
console.log("没有template, el.outerHTML = " + el.outerHTML)
// 将模板编译为 render 函数
template = el.outerHTML;
}else{
console.log("有template = " + template)
}
let render = compileToFunction(template);
opts.render = render;
}
}
运行测试:
至此,我们就拿到了 html 模板
将 html 模板编译为 ast 语法树,用 js 对象的树形结构来描述 HTML 语法
这里就需要对 html 模板进行解析,而解析的方式就是使用正则不断匹配和处理
3,render 函数的重要性
有了 render 函数,当数据变化时,就可以通过 render 函数进行新老对比,
从而可以根据最终对比的结果,再对真实 dom 进行更新
五,结尾
本篇,主要介绍了生成 ast 语法树 - 流程说明,涉及以下几个点:
- Vue 核心渲染流程回顾
- 三种模板写法及优先级
- 两种数据挂载方式
- Vue 的原型方法 $mount
- compileToFunction -> parserHTM流程说明
下一篇,生成 ast 语法树-正则说明