Optimization strategies for the virtual DOM
The virtual DOM is one of Vue 3's core rendering mechanisms, enhancing performance through an efficient Diff algorithm and various optimization strategies. From compile-time optimizations to runtime PatchFlag markers, Vue 3 has undergone deep transformations at the virtual DOM level.
Compile-Time Static Hoisting
Vue 3's compiler analyzes static content in templates and hoists it outside the render function to avoid repeated creation. For example, the following template:
<div>
<span>Static content</span>
<span>{{ dynamic }}</span>
</div>
After compilation, the render function becomes:
const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "Static content", -1 /* HOISTED */)
function render(_ctx) {
return (_openBlock(), _createBlock("div", null, [
_hoisted_1,
_createVNode("span", null, _toDisplayString(_ctx.dynamic), 1 /* TEXT */)
]))
}
The static node _hoisted_1
is hoisted to the module scope and reused directly during each render. The /*#__PURE__*/
annotation hints to bundlers that Tree-Shaking can be applied.
PatchFlag Marking Optimization
Vue 3 adds PatchFlag markers to virtual DOM nodes to quickly skip static content during Diff. Common marker types include:
export const enum PatchFlags {
TEXT = 1, // Dynamic text node
CLASS = 1 << 1, // Dynamic class
STYLE = 1 << 2, // Dynamic style
PROPS = 1 << 3, // Dynamic props (excluding class/style)
FULL_PROPS = 1 << 4, // Props with dynamic keys
HYDRATE_EVENTS = 1 << 5, // Event listeners
STABLE_FRAGMENT = 1 << 6 // Fragment with stable child order
}
When PatchFlags.TEXT
is detected, only the text content is compared, skipping attribute checks:
if (patchFlag & PatchFlags.TEXT) {
if (oldVNode.children !== newVNode.children) {
hostSetElementText(el, newVNode.children)
}
}
Block Tree and Dynamic Anchors
Vue 3 introduces the concept of Blocks, dividing templates into dynamic sections. Each Block tracks its dynamic nodes:
const block = createBlock('div', { id: 'app' }, [
createVNode('p', null, 'Static paragraph'),
openBlock(),
createVNode('span', null, ctx.message, PatchFlags.TEXT)
])
Blocks use dynamic anchors to record node positions. When arrays update, changes are quickly located using key
and anchor information:
<ul>
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
</ul>
The corresponding Diff algorithm prioritizes comparing nodes with the same key, moving DOM elements instead of recreating them.
Event Caching Optimization
Vue 3 caches event handlers to avoid creating new functions during each render:
// Before compilation
<button @click="count++">Click</button>
// After compilation
function render(_ctx) {
return _createVNode("button", {
onClick: _cache[0] || (_cache[0] = ($event) => (_ctx.count++))
}, "Click")
}
Event handlers are cached in the _cache
array and reused during component updates.
Static Type Analysis
Vue 3's compiler performs type analysis on templates to identify optimization opportunities:
- Pure static subtrees (no dynamic bindings in the entire subtree)
- Static props (e.g.,
type
in<input type="text">
) - Static node lists (e.g., all
<li>
under<ul>
with unchanged order)
This information is converted into the virtual DOM's shapeFlag
property:
export const enum ShapeFlags {
ELEMENT = 1,
COMPONENT = 1 << 1,
TEXT_CHILDREN = 1 << 2,
ARRAY_CHILDREN = 1 << 3,
SLOTS_CHILDREN = 1 << 4,
TELEPORT = 1 << 5,
SUSPENSE = 1 << 6,
STATEFUL_COMPONENT = 1 << 7,
FUNCTIONAL_COMPONENT = 1 << 8
}
Efficient Diff Algorithm
Vue 3 employs a dual-ended Diff strategy:
function patchKeyedChildren(oldChildren, newChildren) {
let i = 0
let e1 = oldChildren.length - 1
let e2 = newChildren.length - 1
// Skip identical nodes at the head
while (i <= e1 && i <= e2 && isSameVNode(oldChildren[i], newChildren[i])) {
i++
}
// Skip identical nodes at the tail
while (i <= e1 && i <= e2 && isSameVNode(oldChildren[e1], newChildren[e2])) {
e1--
e2--
}
// Insert remaining new nodes
if (i > e1) {
while (i <= e2) {
patch(null, newChildren[i])
i++
}
}
// Remove remaining old nodes
else if (i > e2) {
while (i <= e1) {
unmount(oldChildren[i])
i++
}
}
// Handle complex sequences
else {
// Create a key-index map
const keyToNewIndexMap = new Map()
for (let j = i; j <= e2; j++) {
keyToNewIndexMap.set(newChildren[j].key, j)
}
// Patch or unmount old nodes
for (let k = i; k <= e1; k++) {
const oldChild = oldChildren[k]
if (keyToNewIndexMap.has(oldChild.key)) {
const newIndex = keyToNewIndexMap.get(oldChild.key)
patch(oldChild, newChildren[newIndex])
} else {
unmount(oldChild)
}
}
// Move and mount new nodes
moveAndMountNewChildren()
}
}
Reactivity System Integration
Virtual DOM updates are deeply integrated with the reactivity system. When reactive data changes:
- The component triggers a scheduled update.
- A new virtual DOM tree is generated.
- Diff comparison is performed with the old tree.
- Only necessary DOM operations are applied.
const state = reactive({ count: 0 })
effect(() => {
// Virtual DOM rendering
const vnode = render(state)
// Diff patching
patch(container, vnode)
})
This collaborative mechanism ensures DOM operations are executed only when data actually changes.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:组合式API的设计哲学
下一篇:TypeScript支持的重要性