【Vue原理】Compile - 白话版
写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧
终于到了要讲 compile 白话的时候了,大家准备好了吗,白话版肯定不会很复杂啦,源码版就不一定了。。。
源码版我写了9篇啊!每篇的篇幅都很长啊!!我都快写奔溃了啊!!
都快坚持不下来了,我算了算, compile 的源码版,我好像快写了一个多月???
卧槽,竟然写了这么久.....
好吧,现在开始我们的正文
Compile
compile 的内容非常多,大致分为三块主要内容,我也称他们是Vue的 渲染三巨头
就是 parse,optimize,generate
虽然分为三块,但是要明确一点
compile 的作用是解析模板,生成渲染模板的 render
比如这样的模板
经过 compile 之后,就会生成下面的 render
_c('div', [_c('span'), _v(num)])
而 render 的作用,也是为了生成跟模板节点一一对应的 Vnode
{ tag: "div", children:[{ tag: "span", text: undefined },{ tag: undefined text: "111" }] }
下面我们就来一个个看渲染三巨头
Parse
这是 compile 的第一个步骤
作用是
接收 template 原始模板,按照模板的节点 和数据 生成对应的 ast
比如这样
生成的 ast 是这样,所有模板中出现的数据,你都可以在 ast 中找到
{ tag: "div", attrsMap: {test: "2"}, children:[{ tag: "span", children: [], attrsMap: {name: "1"} }] }
ast 是什么?个人简单理解的话
以数据的形式去描述一个东西的所有的特征吧,说错别打我
比如说ast 描述我
{ name: "神仙朱", sex: 1, desc: "一个靓仔" }
具体可以查一下,相关内容挺多的
另外,这里不会讲细节,parse 是怎么生成 ast 的,因为涉及很多源码,放在源码版了
Optimize
这是 compile 的第二步
作用是
遍历递归每一个ast节点,标记静态的节点(没有绑定任何动态数据),
这样就知道那部分不会变化,于是在页面需要更新时,减少去比对这部分DOM
从而达到性能优化的目的
比如这个模板
span 和 b 就是静态节点,在 optimize 处理中,就会给他们添加 static 判断是否是静态节点
{ static: false, staticRoot: false, tag: "div", children: [{ staticRoot: true, tag: "span", children: [{ static: true, tag: "b" }] },{ static: false, text: "{{a}}" }] }
而你也看到一个属性,staticRoot,这个是表示这个节点是否是静态根节点的意思
用来标记 某部分静态节点 最大的祖宗节点,后面更新的时候,只要碰到这个属性,就知道他的所有子孙节点都是静态节点了,而不需要每个子孙节点都要判断一次浪费时间
具体是怎么做的,感兴趣的话欢迎看以后的源码版
Generate
这是 compile 的第三步
作用是
把前两步生成完善的 ast 组装成 render 字符串(这个 render 变成函数后是可执行的函数,不过现在是字符串的形态,后面会转成函数)
看个例子
经过前两步变成 ast
{ static: false, staticRoot: false, tag: "div", children: [{ static: false, staticRoot: false, tag: "span", children: [{ static: false, text: "{{b}}" }] },{ static: false, text: "{{a}}" }] }
然后,generate 接收 ast,先处理最外层 ast,然后开始递归遍历子节点,直到所有节点被处理完
这个过程中,字符串会被一点一点拼接完成,比如上面的 ast 拼接结果就是下面这样
_c 是生成节点对应的 Vnode 的一个函数
` _c('div', [ _c('span', [ _v(b) ]), _v(a) ]) `
简单说一下拼接流程
1、一开始接收到 ast,处理最外层 ast 这个点,是 div,于是拼接得到字符串
code = ` _c('div', [ `
2、遍历 div 子节点,遇到 span,拼接在 div 的子节点数组中
code = `_c('div', [ _c('span', [ `
3、开始处理 span 的子节点 b,放进 span 的 子节点数组中
code = ` _c('div', [ _c('span', [ _v(b) `
4、span 子节点处理完,闭合 span 的 子节点数组
code = ` _c('div', [ _c('span', [ _v(b) ] `
5、继续处理 span 同级 的子节点,是个文本节点,但是是动态值,变量是 a
code = `_c('div', [ _c('span', [ _v(b) ] , _v(a) `
6、所有子节点都处理完毕,闭合 div 的 子节点数组
code = ` _c('div', [ _c('span', [ _v(b) ] , _v(a) )] `
render转成函数
前面两步把 template 解析生成了 render 字符串,但是需要执行的话,还是需要转换成函数的
怎么转呢?就是下面这样
render = new Function(render)
然后 render 保存在实例上,具体位置是
vm.$options.render
至此,compile 所有的功能就完成了
而关于 render 的内容,比如说 render 中出现的各种函数是什么,会专门放在 render 的文章去记录