template模板通过compile编译,最终得到render function,这里不是响应式式核心的内容,但是可以了解其大概的流程。compile主要分了parse、optimize、generate三个阶段。
借一张图表示一下编译的三个阶段如下:
parse函数生成AST
compile 函数(src/compiler/index.js)就是将 template 编译成 render function 的字符串形式。
接下来就详细讲解这个函数:
|
|
createCompiler 函数主要通过3个步骤:parse、optimize、generate来生成一个包含ast、render、staticRenderFns的对象。
parse函数
AST
在说parse函数之前,我们先来了解一个概念:AST(Abstract Syntax Tree)抽象语法树:
AST 的全称是 Abstract Syntax Tree(抽象语法树),是源代码的抽象语法结构的树状表现形式,计算机学科中编译原理的概念。Vue 源码中借鉴 jQuery 作者 John Resig 的 HTML Parser 对模板进行解析,得到的就是 AST 代码。
接着我们看一下Vue中对AST数据的定义:
可以看到 ASTNode 有三种形式:ASTElement,ASTExpression,ASTText。通过属性type来进行标识。
函数功能
下面我们正式进入parse函数功能:
我们省略了parse的相关内容,只看一下大体的功能,其主要的功能函数应该是parseHTML方法。接受了2个参数,一个使我们的模板template,另一个是包含start、end、chars的方法。
相关正则表达式
在看parseHTML之前,我们需要先了解一下下面这几个正则:
Vue 通过上面几个正则表达式去匹配开始结束标签、标签名、属性等等。
parseHtml函数
有了上面这些基础,我们再来看看parseHtml的内部实行:
上面只看一下代码的大概意思:
- 首先通过while (html)去循环判断html内容是否存在。
- 再判断文本内容是否在script/style标签中
- 上述条件都满足的话,开始解析html字符串
假设我们传递这样一个html字符串<div id="demo"></div>
。我们来看其中一段关于Start tag解析的方法:
这里面有parseStartTag 和 handleStartTag两个方法值得关注一下:
经过上面一步的解析,我们得到了一个起始标签match的数据结构:
再看一下handleStartTag函数
到这里似乎一切明朗了许多,parseHTML主要用来蚕食html字符串,解析出字符串中的tagName,attrs,match等元素,传回parseHTML函数中的start方法:
其实start方法就是处理 element 元素的过程。确定命名空间;创建AST元素 element;执行预处理;定义root;处理各类 v- 标签的逻辑;最后更新 root、currentParent、stack 的结果。 最终通过 createASTElement 方法定义了一个新的 AST 对象:
小结
下面我们来屡一下parse整体的过程:
- 通过parseHtml来一步步解析传入html字符串的标签、元素、文本、注释..
- parseHtml解析过程中,调用传入的start,end,chars方法来生成AST语法树
我们看一下最终生成的AST语法树对象:
optimize标记节点
上一节主要关注了compile的parse部分,我们完成了一个字符串模板解析成一个AST语法树的过程,接下来这一步是优化的关键,我们需要通过optimize方法,将AST节点进行静态节点标记。为后面 patch 过程中对比新旧 VNode 树形结构做优化。被标记为 static 的节点在后面的 diff 算法中会被直接忽略,不做详细的比较。
optimize功能
我们去简单分析一下optimize功能
isStatic
首先标记所有静态节点:
ASTNode 的 type 字段用于标识节点的类型,可查看上一篇的 AST 节点定义:type 为 1 表示元素,type 为 2 表示插值表达式,type 为 3 表示普通文本。可以看到,在标记 ASTElement 时会依次检查所有子元素节点的静态标记,从而得出该元素是否为 static。
markStaticRoots
上面 markStatic 函数使用的是树形数据结构的深度优先遍历算法,使用递归实现。 接下来继续标记静态树:
markStaticRoots 函数里并没有什么特别的地方,仅仅是对静态节点又做了一层筛选。
小结
optimizer旨在为语法树的节点标上static和staticRoot属性。 遍历第一轮,标记static属性:
- 判断node是否为static(有诸多条件)
- 标记node的children是否为static,若存在non static子节点,父节点更改为static = false
遍历第二轮,标记staticRoot
- 标记static或节点为staticRoot,这个节点type === 1(一般是含有tag属性的节点)
- 具有v-once指令的节点同样被标记staticRoot
- 为了避免过度优化,只有static text为子节点的节点不被标记为staticRoot
- 标记节点children的staticRoot
generate生成渲染函数过程
generate 函数
generate 函数(src/compiler/codegen/index.js)主要功能就是根据 AST 结构拼接生成 render function 的字符串。
其中 genElement 函数(src/compiler/codegen/index.js)是会根据 AST 的属性调用不同的方法生成字符串返回。也就是拼接字符串了:
以上就是 compile 函数中三个核心步骤的介绍,compile 之后我们得到了 render function 的字符串形式,后面通过 new Function 得到真正的渲染函数。DOM 初始化过程最后一步是根据渲染函数生成 Vnode,根据此 Vnode 生成真实 DOM,插入 DOM 树中,并将该 Vnode 记录为 preVnode。
总结
经历过这些过程以后,我们已经把 template 顺利转成了 render function 了,接下来我们将介绍 patch 的过程,来看一下具体 VNode 节点如何进行差异的比对。