Skip to content

Commit

Permalink
Vue模板编译原理
Browse files Browse the repository at this point in the history
  • Loading branch information
yzsunlei committed Dec 18, 2019
1 parent ee1be93 commit 049265d
Showing 1 changed file with 43 additions and 28 deletions.
71 changes: 43 additions & 28 deletions 1.5.Vue模板编译原理-AST生成Render字符串.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
## 编译过程
* 模板编译是Vue中比较核心的一部分。关于 Vue 编译原理这块的整体逻辑主要分三个部分,也可以说是分三步,前后关系如下:
模板编译是Vue中比较核心的一部分。关于 Vue 编译原理这块的整体逻辑主要分三个部分,也可以说是分三步,前后关系如下:

> 第一步:将模板字符串转换成element ASTs(解析器)
> 第二步:对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
> 第三步:使用element ASTs生成render函数代码字符串(代码生成器)
* 对应的Vue源码如下,源码位置在`src/compiler/index.js`
对应的Vue源码如下,源码位置在`src/compiler/index.js`

```vuejs
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
Expand All @@ -29,16 +30,19 @@ export const createCompiler = createCompilerCreator(function baseCompile (
})
```

* 这篇文档主要讲第三步使用element ASTs生成render函数代码字符串,对应的源码实现我们通常称之为代码生成器。
这篇文档主要讲第三步使用element ASTs生成render函数代码字符串,对应的源码实现我们通常称之为代码生成器。

## 代码生成器运行过程
* 在分析代码生成器的原理前,我们先举例看下代码生成器的具体作用。
在分析代码生成器的原理前,我们先举例看下代码生成器的具体作用。

```html
<div>
<p>{{name}}</p>
</div>
```
* 在上节"Template生成AST"中,我们已经说了通过解析器会把上面模板解析成抽象语法树(AST),解析结果如下:

在上节"Template生成AST"中,我们已经说了通过解析器会把上面模板解析成抽象语法树(AST),解析结果如下:

```javascript
{
tag: "div"
Expand Down Expand Up @@ -69,18 +73,22 @@ export const createCompiler = createCompilerCreator(function baseCompile (
]
}
```

* 而在这一节我们会将AST转换成可以直接执行的JavaScript字符串,最终结果如下:

```javascript
with(this) {
return _c('div', [_c('p', [_v(_s(name))]), _v(" "), _m(0)])
}
```
* 现在看不懂不要紧。其实生成器的过程就是 generate 函数拿到解析好的 AST 对象,递归 AST 树,为不同的 AST 节点创建不同的内部调用方法,
然后组合成可执行的JavaScript字符串,等待后面的调用。

现在看不懂不要紧。其实生成器的过程就是 generate 函数拿到解析好的 AST 对象,递归 AST 树,为不同的 AST 节点创建不同的内部调用方法,然后组合成可执行的JavaScript字符串,等待后面的调用。

## 内部调用方法
* 我们看上面示例生成的JavaScript字符串,会发现里面会有`_v``_c``_s`这样的东西,这些其实就是Vue内部定义的一些调用方法。
* 其中 `_c` 函数定义在 `src/core/instance/render.js` 中。
我们看上面示例生成的JavaScript字符串,会发现里面会有`_v``_c``_s`这样的东西,这些其实就是Vue内部定义的一些调用方法。

其中 `_c` 函数定义在 `src/core/instance/render.js` 中。

```vuejs
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
Expand All @@ -89,7 +97,9 @@ with(this) {
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
```
* 而其他 `_s``_v` 是定义在 `src/core/instance/render-helpers/index.js` 中:

而其他 `_s``_v` 是定义在 `src/core/instance/render-helpers/index.js` 中:

```vuejs
export function installRenderHelpers (target: any) {
target._o = markOnce
Expand All @@ -111,11 +121,12 @@ export function installRenderHelpers (target: any) {
target._p = prependModifier
}
```
* 以上都是会在生成的JavaScript字符串中用到的,像比较常用的 `_c` 执行 `createElement` 去创建 `VNode``_l` 对应 `renderList` 渲染列表;
`_v` 对应 `createTextVNode` 创建文本 `VNode``_e` 对于 `createEmptyVNode` 创建空的 `VNode`

以上都是会在生成的JavaScript字符串中用到的,像比较常用的 `_c` 执行 `createElement` 去创建 `VNode``_l` 对应 `renderList` 渲染列表;`_v` 对应 `createTextVNode` 创建文本 `VNode``_e` 对于 `createEmptyVNode` 创建空的 `VNode`

## 整体逻辑
* 代码生成器的入口函数是`generate`,具体定义如下,源码位置在`src/compiler/codegen/index.js`
代码生成器的入口函数是`generate`,具体定义如下,源码位置在`src/compiler/codegen/index.js`

```vuejs
export function generate (
ast: ASTElement | void,
Expand All @@ -134,10 +145,11 @@ export function generate (
}
}
```
* 生成器入口函数就比较简单,先初始化一些配置options,然后传入ast进行生成,没有ast时直接生成一个空div,最后返回生成的JavaScript
字符串和静态节点的渲染函数。这里会把静态节点区分出来,便于后面的Vnode diff,即Vue比较算法更新DOM,现在先略过。

* 现在我们重点看看genElement函数,代码位置在`src/compiler/codegen/index.js`
生成器入口函数就比较简单,先初始化一些配置options,然后传入ast进行生成,没有ast时直接生成一个空div,最后返回生成的JavaScript字符串和静态节点的渲染函数。这里会把静态节点区分出来,便于后面的Vnode diff,即Vue比较算法更新DOM,现在先略过。

现在我们重点看看genElement函数,代码位置在`src/compiler/codegen/index.js`

```vuejs
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
Expand Down Expand Up @@ -182,11 +194,12 @@ export function genElement (el: ASTElement, state: CodegenState): string {
}
}
```
* 你会发现genElement函数里面有很多条件判断。这是因为Vue里面的指令写法实在太多,像 `v-if``v-for``v-slot`等,每种指令写法
都分离出一个函数来单独处理,这样代码阅读起来也清晰明了。下面,我们重点来看看 `genFor``genData` 的具体处理逻辑。

你会发现genElement函数里面有很多条件判断。这是因为Vue里面的指令写法实在太多,像 `v-if``v-for``v-slot`等,每种指令写法都分离出一个函数来单独处理,这样代码阅读起来也清晰明了。下面,我们重点来看看 `genFor``genData` 的具体处理逻辑。

## genFor
* genFor 函数是用来处理 v-for 指令写法的,源码位置在`src/compiler/codegen/index.js`
genFor 函数是用来处理 v-for 指令写法的,源码位置在`src/compiler/codegen/index.js`

```vuejs
export function genFor (
el: any,
Expand Down Expand Up @@ -221,10 +234,12 @@ export function genFor (
'})'
}
```
* genFor 的逻辑其实就是,首先 AST 元素节点中获取了和 for 相关的一些属性,然后返回了一个代码字符串。

genFor 的逻辑其实就是,首先 AST 元素节点中获取了和 for 相关的一些属性,然后返回了一个代码字符串。

## genData
* genData 函数是用来处理节点属性的,源码位置在`src/compiler/codegen/index.js`
genData 函数是用来处理节点属性的,源码位置在`src/compiler/codegen/index.js`

```vuejs
export function genData (el: ASTElement, state: CodegenState): string {
let data = '{'
Expand Down Expand Up @@ -315,12 +330,12 @@ export function genData (el: ASTElement, state: CodegenState): string {
```

## 总结一下
* 代码生成是模板编译的第三步,它完成了AST到Render的转换,即将抽象语法树AST转换成可以直接执行的JavaScript字符串。
代码生成是模板编译的第三步,它完成了AST到Render的转换,即将抽象语法树AST转换成可以直接执行的JavaScript字符串。

* 其中genElement的代码比较多,因为需要分别处理的情况非常多,这里只是对genFor和genData的代码逻辑进行了说明。
其中genElement的代码比较多,因为需要分别处理的情况非常多,这里只是对genFor和genData的代码逻辑进行了说明。

## 相关资料
* [https://ustbhuangyi.github.io/vue-analysis/compile/codegen.html#generate](https://ustbhuangyi.github.io/vue-analysis/compile/codegen.html#generate)
* [https://github.com/liutao/vue2.0-source/blob/master/compile%E2%80%94%E2%80%94%E7%94%9F%E6%88%90render%E5%AD%97%E7%AC%A6%E4%B8%B2.md](https://github.com/liutao/vue2.0-source/blob/master/compile%E2%80%94%E2%80%94%E7%94%9F%E6%88%90render%E5%AD%97%E7%AC%A6%E4%B8%B2.md)
* [https://github.com/lihongxun945/myblog/issues/29](https://github.com/lihongxun945/myblog/issues/29)
* [https://segmentfault.com/a/1190000012922342](https://segmentfault.com/a/1190000012922342)
## 相关
- [https://ustbhuangyi.github.io/vue-analysis/compile/codegen.html#generate](https://ustbhuangyi.github.io/vue-analysis/compile/codegen.html#generate)
- [https://github.com/liutao/vue2.0-source/blob/master/compile%E2%80%94%E2%80%94%E7%94%9F%E6%88%90render%E5%AD%97%E7%AC%A6%E4%B8%B2.md](https://github.com/liutao/vue2.0-source/blob/master/compile%E2%80%94%E2%80%94%E7%94%9F%E6%88%90render%E5%AD%97%E7%AC%A6%E4%B8%B2.md)
- [https://github.com/lihongxun945/myblog/issues/29](https://github.com/lihongxun945/myblog/issues/29)
- [https://segmentfault.com/a/1190000012922342](https://segmentfault.com/a/1190000012922342)

0 comments on commit 049265d

Please sign in to comment.