Vue模版编译,也就是compilte阶段,它其实就是将template转化为render function 的过程,它会经过以下三个阶段:
-
parse阶段将template字符串通过各种正则表达式生成一颗抽象语法树AST,在解析的过程中是通过while不断循环这个字符串,每解析完一个标签指针向下移动;并且用栈来建立节点间的层级关系,也就是用来保存解析好的标签头。 -
optimize阶段将生成的抽象语法树AST进行静态标记,这些被标记为静态的节点在后面的patch过程中会被跳过对比,从而达到优化的目的。标记的主要过程是为每个节点设置类似于static这样的属性,或者给根节点设置一个staticRoot属性表明这是不是一个静态根。 -
在进入到
generate阶段之前,说明已经生成了被静态标记过的AST,而generate就是将AST转化为render function字符串。
optimize主要是为了给parse阶段生成的AST进行静态标记。
这些被标记为静态的节点在后面的patch过程中会被跳过对比,从而达到优化的目的。
AST抽象语法树,实际就是一个JavaScript对象,而进行静态标记,就是给其中的每个节点定义一个类似于叫static的属性,或者给根节点设置一个staticRoot属性表明这是不是一个静态根。
例如下面👇的这颗AST,在刚开始是没有static属性的,经过optimize阶段就有了:
{
'type': 1,
'attrsMap': {
'class': 'wrap'
},
'staticClass': 'wrap'
'tag': 'div',
+ 'static': false,
+ 'staticRoot': false,
'children': [
{
'type': 3,
'attrsMap': {
'class': 'header'
},
'staticClass': 'header',
'tag': 'div',
+ 'static': true
},
{
'type': 2,
'attrsMap': {
'class': 'footer',
'v-if': 'isShow'
},
'ifConditions': [
'exp': 'isShow'
],
'staticClass': 'footer',
'tag': 'div',
+ 'static': false
}
]
}实现一个optimize函数,首先我们需要一个用来判断是否为「静态」的函数。
判断一个节点是否为「静态」,判断的标准是:
- 节点的
type为2(表达式节点),为非静态节点,返回false。 - 节点的
type为3(文本节点),为静态节点,返回true。 - 若是节点中存在
if或者for这样的条件时,也为非静态节点,返回false。
// 判断是否是静态节点
function isStatic (node) {
const { type } = node
if (type === 2) { // 表达式节点
return false
} else if (type === 3) { // 文本节点
return true
}
return (!node.if && !node.for) // 不存在if和for
}有了判断是否为「静态」的函数之后,就需要遍历所有节点,然后打上标记了。
标记的依据是:
- 首先通过
isStatic判断当前节点是否为静态的节点,设置好static属性 - 然后会遍历所有的子节点,设置好
static属性,如果其中有子节点为非静态的,则当前节点也是非静态的。
来看看伪代码:
function markStatic (node) {
node.static = isStatic(node) // 标记当前节点的static
if (node.type === 1) {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i]
markStatic(child) // 标记子节点
if (!child.static) {
node.static = false
}
}
}
}父节点为非静态节点时,它下面的所有子节点就一定为非静态吗?
不一定,例如下面这种情况,就不是:
<div class="wrap">
<div class="header">我是头部静态节点</div>
<div class="footer" v-if="isShow">我是尾部非静态节点</div>
</div>除了使用markStatic函数来给每个节点标记static属性之外,还可以用一个名为markStaticRoots的函数来标记是否为静态根(staticRoot),将staticRoot属性设置为true。
试想一下,下面这个结构:
<div class="wrap">
<div class="header">我是头部静态节点</div>
<div class="footer">我是尾部静态节点</div>
</div>这段template下的节点全为静态节点,我们就可以将其标记为静态根。
判断的条件是:
- 传入的节点本身的
static就是为true - 并且该节点有子节点且不止有一个文本节点
function markStaticRoots () {
if (node.type === 1) {
if (node.static && node.children && !(
node.children.length === 1 && node.children[0].type === 3
)) {
node.staticRoot = true
return
} else {
node.staticRoot = false
}
}
}现在可以来实现optimize函数了:
function optimize (rootAst) {
markStatic(rootAst)
markStaticRoots(rootAst)
}Vue.js 实现了一个 nextTick 函数,传入一个 cb ,这个 cb 会被存储到一个队列中,在下一个 tick 时触发队列中的所有 cb 事件。
let callbacks = [];
let pending = false;
function nextTick (cb) {
callbacks.push(cb);
if (!pending) {
pending = true;
setTimeout(flushCallbacks, 0);
}
}
function flushCallbacks () {
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
for (let i = 0; i < copies.length; i++) {
copies[i]();
}
}这样做的目的是:
例如在一个时间点内,一直调用nextTick
nextTick(cb)
nextTick(cb)
nextTick(cb)
由于setTimeout的原因,pending变为了true之后就不会执行if里的代码了,而是等定时器执行了之后才变回来