静态节点标记与优化
Vue通过 markStatic 递归标记AST中不包含动态绑定的节点,实现渲染优化和虚拟DOM diff跳过。
markStatic 标记过程
JavaScript
function markStatic(node: ASTNode) {
node.static = isStatic(node)
if (node.type === 1) {
// 排除slot和component
if (
node.tag !== 'slot' &&
node.tag !== 'component' &&
!isPlatformReservedTag(node.tag) === false
) {
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
if (!child.static) {
node.static = false
}
}
}
if (node.ifConditions) {
walkBlockConditions(node.ifConditions, node)
}
}
}
function isStatic(node): boolean {
if (node.type === 2) return false // 含表达式的文本
if (node.type === 3) return true // 纯文本
return !!(
node.pre ||
(
!node.hasBindings &&
!node.if && !node.for &&
!isBuiltInTag(node.tag) &&
isPlatformReservedTag(node.tag) &&
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
)
)
}
递归遍历AST,子节点有非静态则父节点也标记为非静态。
静态节点判定条件
JavaScript
// 非静态特征
const isBuiltInTag = makeMap('slot,component')
const isStaticKey = makeMap(
'type,tag,attrsList,attrsMap,plain,parent,' +
'children,attrs,staticRoot,staticProcessed'
)
function isStatic(node): boolean {
// type=2 包含 {{ }} 表达式 → 非静态
// type=3 纯文本 → 静态
// 包含 v-bind/v-on 等动态绑定 → 非静态
// 包含 v-if/v-for → 非静态
// slot/component 标签 → 非静态
// 原生标签且无动态属性 → 静态
}
静态节点必须是无动态绑定、无v-if/v-for的原生标签或纯文本。
markStaticRoots 静态根节点
JavaScript
function markStaticRoots(node, index) {
if (node.type === 1) {
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true
return
} else {
node.staticRoot = false
}
node.children.forEach((child, i) => {
markStaticRoots(child, i)
})
}
}
静态根节点是包含子节点的静态节点,用于优化diff过程。
静态根节点优化条件
JavaScript
// 不为静态根的情况
// 1. 只有一个纯文本子节点
// <div>hello</div> // staticRoot = false
// 2. 没有子节点
// <br/> // staticRoot = false
// 为静态根的情况
// <div>
// <span>hello</span> // 多个子节点或子节点非纯文本
// <p>world</p>
// </div> // staticRoot = true
避免将单一纯文本子节点标记为静态根,优化收益不足。
生成静态渲染函数
JavaScript
function genStatic(el, state) {
el.staticProcessed = true
state.staticRenderFns.push(
`with(this){return ${genElement(el, state)}}`
)
return `_m(${state.staticRenderFns.length - 1})`
}
静态节点生成独立的渲染函数,通过 _m(index) 调用。
渲染优化效果
JavaScript
// 原始模板
<div>
<h1>标题</h1> // 静态节点
<p>{{ msg }}</p> // 动态节点
<footer>版权信息</footer> // 静态节点
</div>
// 渲染函数
function render() {
with(this) {
return _c('div', [
_m(0), // 静态节点h1,调用staticRenderFns[0]
_c('p', [_v(_s(msg))]), // 动态节点,每次渲染
_m(1) // 静态节点footer,调用staticRenderFns[1]
])
}
}
静态节点通过 _m() 调用,只在首次渲染时执行,diff时直接复用。
要点总结
markStatic递归标记AST中无动态绑定的节点为静态- 静态节点判定:无v-bind/v-on、无v-if/v-for、非slot/component
- 子节点有非静态则父节点也标记为非静态
markStaticRoots标记包含多子节点的静态节点为静态根- 静态根节点生成独立渲染函数,通过
_m(index)调用 - diff时静态节点直接跳过比较,提升渲染性能
📝 发现内容有误?点击此处直接编辑