Vue 模板编译
文章目录
总体概述
- 获取 template:
- 首先要拿到 Vue 构造函数中 $options 参数中的 el,判断有没有 el,如果有再进行下一步操作
- 然后在 Vue 的原型上添加 $mount 方法用于渲染,在这个方法中,需要做如下操作:
- 获取 $options,并在 Vue 上挂载 $el
- 判断 $options 中有无 render 函数(目前不考虑有 render 函数的情况),有的话就使用 render 函数;然后判断有无 template,有的话就使用 template;最后判断在 body 标签中有无 el 所对应的元素节点,如果有就把该元素的 outerHTML 赋值给 template
- template 转 AST(Abstract Syntax Tree,抽象语法树) 树:
- 一边匹配一边删除,全部处理完就成为空字符串
- 用 parseStartTag 处理开始标签获取属性,处理完后用 start 函数
- 遇到文本节点用 chars 函数处理
- 遇到结束标签用 end 函数处理
- AST 树转 render 函数(_c、_v、_s):
- 获取元素的子元素并将其全部转化为字符串【递归处理】
- 将属性转为字符串
- 最后将元素节点进行字符串拼接
- render 函数转虚拟节点:
- 使用with函数将this指向vm,然后调用函数产生虚拟节点(vnode)
- 其中需要在原型上添加上 _c、_v、_s 方法
- 设置 PATCH,打补丁到真实 DOM
- 利用递归将虚拟DOM转化为真实DOM,然后替换 el
- 转化的时候需要将各自的属性添加至节点上
1. 模板转 AST 树
函数名:parseHTMLToAST(html)
1.1 实现思路
- 正则匹配 template 模板中的标签、属性和文本节点,正则匹配的所有功能如下,详情可查看 Vue 源码:
// 匹配id="app"/id='app'/id=app
const attribute =
/^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`
const qnameCapture = `((?:${
ncname}\\:)?${
ncname})`
// 匹配开始标签的起始部分,如:<div
const startTagOpen = new RegExp(`^<${
qnameCapture}`)
// 匹配开始标签的结束部分,如:>、/>
const startTagClose = /^\s*(\/?)>/
// 匹配结束标签,如:</div>
const endTag = new RegExp(`^<\\/${
qnameCapture}[^>]*>`)
-
将 template 中匹配一部分删除一部分(advance 函数),匹配开始标签的流程如下:
- 函数名:parseStartTag()
- 首先 template 字符串中判断
<
的下标是为为 0,如果为 0 则代表即将匹配标签的开始标签(大于 0 的情况下面会说)或者为结束标签,则匹配结束标签(endTag),放入 end 函数中处理 - 然后匹配开始标签中的标签名(startTagOpen)、属性名和属性值(attribute),在匹配属性的时候要一直判读是否匹配到开始标签的结束部分,如果没有匹配到那就一直匹配属性
- 最后如果匹配到开始标签的结束项
>
或者/>
(startTagClose),这样匹配结束,返回 match 对象,结构如下:
match = { tagName: 'xxx', attrs: [ { name: 'aaa', value: '......' } ] // attrs 中的 value 是字符串类型,不存在对象类型,如果匹配到像 style 属性,最后转成 AST 树后 attrs 中的 value 是对象类型,处理在 render 函数中将字符串转为对象形式 }
- 将返回的对象放入 start 函数中处理
-
如果大于 0,则代表匹配文本内容,那么从开始到
<
前到内容都为文本,将其截取后放入 chars 函数中处理 -
最后生成 AST 树,大致结构如下:
{
tag: 'div',
type: 1, // 代表元素节点
attrs: [
{
name: 'id',
value: 'app'
},
{
name: 'style',
value: {
color: 'red',
fontSize: '20px'
}
}
],
children: [
// 子元素
......
]
}
1.2 start 函数
- 利用 createASTElement 函数创建 AST 元素,即返回如下对象:
{
tag: tagName,
type: 1,
children: [],
attrs
}
- 利用栈存储节点,每次匹配到开始标签的标签名,就将标签名入栈
- 然后当前的父节点就是此元素
function start(tagName, attrs) {
console.log('---------开始---------');
console.log(tagName, attrs);
const element = createElement(tagName, 1, attrs);
!root && (root = element);
currentParent = element;
stack.push(element)