一、总述
上一章节我们讲到通过解析将template转成AST模型树,接下来继续对模型树优化,进行静态标注。那么问题来了,什么是静态标注?为什么要静态标注。
在源码的注释中我们找到了下面这段话:
/**
* Goal of the optimizer: walk the generated template AST tree
* and detect sub-trees that are purely static, i.e. parts of
* the DOM that never needs to change.
*
* Once we detect these sub-trees, we can:
*
* 1. Hoist them into constants, so that we no longer need to
* create fresh nodes for them on each re-render;
* 2. Completely skip them in the patching process.
*/
1、永远不需要变化的DOM就是静态的。
2、重新渲染时,作为常量,无需创建新节点;在patch的过程中可以忽略他们(后面会专门介绍)。
接下来我们开始源码之旅,这个模块的代码相对简单。
二、markStatic
optimize方法位于src/compiler/optimize.js,
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
isStaticKey = genStaticKeysCached(options.staticKeys || '')
isPlatformReservedTag = options.isReservedTag || no
//1、标注静态节点
markStatic(root)
//2、标注静态根节点
markStaticRoots(root, false)
}
整个过程分为两部分,第一部分标注静态节点,第二部分标注静态根节点。首先看markStatic这个方法。
function markStatic (node: ASTNode) {
//1、标注节点的状态
node.static = isStatic(node)
//2、对标签节点进行处理
if (node.type === 1) {
// do not make component slot content static. this avoids
// 1. components not able to mutate slot nodes
// 2. static slot content fails for hot-reloading
if (
//非平台保留标签(html,svg)
!isPlatformReservedTag(node.tag) &&
//不是slot标签
node.tag !== 'slot' &&
//不是一个内联模板容器
node.attrsMap['inline-template'] == null
) {
return
}
//递归其子节点,标注状态
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
//子节点非静态,则该节点也标注非静态
if (!child.static) {
node.static = false
}
}
//对ifConditions进行循环递归
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block
markStatic(block)
if (!block.static) {
node.static = false
}
}
}
}
}
1、第一步,判断节点状态并标注。
function isStatic (node: ASTNode): boolean {
if (node.type === 2) { //表达式,标注非静态
return false
}
if (node.type === 3) { // 普通文本,标注静态
return true
}
return !!(node.pre || (//v-pre 指令||
!node.hasBindings && // (无动态绑定&&
!node.if && !node.for && // 没有 v-if 和 v-for &&
!isBuiltInTag(node.tag) && // 不是内置的标签,内置的标签有slot和component &&
isPlatformReservedTag(node.tag) && //是平台保留标签&&
!isDirectChildOfTemplateFor(node) &&//不是 template 标签的直接子元素并且没有包含在 for 循环中
Object.keys(node).every(isStaticKey)//结点包含的属性只能有isStaticKey中指定的几个) //以上条件满足则为静态标签
))
}
节点类型为表达式,标注为非静态;普通文本为静态。
v-pre指令(无需编译)标注为静态,或者满足以下条件,也标注为静态。
(1)无动态绑定
(2)没有 v-if 和 v-for
(3) 不是内置的标签,内置的标签有slot和component
(4)是平台保留标签(html和svg标签)
(5)不是 template 标签的直接子元素并且没有包含在 for 循环中
(6)结点包含的属性只能有isStaticKey中指定的几个.
2、第二步,对节点类型为标签的进行处理。
首先对slot内容不做递归标注,直接返回;其他的则循环其子节点,进行递归标注,如果子节点为非静态,那么该节点也要标注非静态。所以整个标注过程是自下而上,先标注子节点,然后再是父节点,一层一层往上回溯。
对ifConditions的循环也是类似过程。
三、markStaticRoots
继续看markStaticRoots方法
function markStaticRoots (node: ASTNode, isInFor: boolean) {
if (node.type === 1) {
//用以标记在v-for内的静态节点,个属性用以告诉renderStatic(_m)对这个节点生成新的key,避免patch error
if (node.static || node.once) {
node.staticInFor = isInFor
}
//一个节点要成为根节点,那么要满足以下条件:
//1、静态节点,并且有子节点,
//2、子节点不能仅为一个文本节点
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true
return
} else {
node.staticRoot = false
}
//循环递归标记
if (node.children) {
for (let i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for)
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}
一个节点要成为静态根节点,需要满足以下条件:
1、自身为静态节点,并且有子节点,
2、子节点不能仅为一个文本节点
对于第二个条件,主要考虑到标记静态根节点的受益较小。
接下来递归循环其子节点,循环标记。
以前一章节的template为例,标记完成后的AST模型如下:
{
"type": 1,
"tag": "div",
"attrsList": [
{
"name": "id",
"value": "app"
}
],
"attrsMap": {
"id": "app"
},
"children": [
{
"type": 1,
"tag": "ul",
"attrsList": [],
"attrsMap": {},
"parent": {
"$ref": "$"
},
"children": [
{
"type": 1,
"tag": "li",
"attrsList": [],
"attrsMap": {
"v-for": "item in items"
},
"parent": {
"$ref": "$[\"children\"][0]"
},
"children": [
{
"type": 2,
"expression": "\"\\n itemid:\"+_s(item.id)+\"\\n \"",
"tokens": [
"\n itemid:",
{
"@binding": "item.id"
},
"\n "
],
"text": "\n itemid:{{item.id}}\n ",
"static": false
}
],
"for": "items",
"alias": "item",
"plain": true,
"static": false,
"staticRoot": false,
"forProcessed": true
}
],
"plain": true,
"static": false,
"staticRoot": false
}
],
"plain": false,
"attrs": [
{
"name": "id",
"value": "\"app\""
}
],
"static": false,
"staticRoot": false
}
可以看到每个节点增加了static,staticRoot两个属性,由于叶节点包含了表达式,所以所有的父节点都标记为false。
五、总结
optimize整个过程逻辑较简单,代码也不多,给节点标记上static后,为后续的render和patch做准备,提升效率。