阿里云主机折上折
  • 微信号
Current Site:Index > Optimization strategies for the virtual DOM

Optimization strategies for the virtual DOM

Author:Chuan Chen 阅读数:30863人阅读 分类: Vue.js

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:

  1. Pure static subtrees (no dynamic bindings in the entire subtree)
  2. Static props (e.g., type in <input type="text">)
  3. 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:

  1. The component triggers a scheduled update.
  2. A new virtual DOM tree is generated.
  3. Diff comparison is performed with the old tree.
  4. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.