工程化与框架系列(13)--虚拟DOM实现
- 创业
- 2025-09-19 15:09:03

虚拟DOM实现 🌳
虚拟DOM(Virtual DOM)是现代前端框架的核心技术之一,它通过在内存中维护UI的虚拟表示来提高渲染性能。本文将深入探讨虚拟DOM的实现原理和关键技术。
虚拟DOM概述 🌟💡 小知识:虚拟DOM是对真实DOM的一种轻量级抽象表示,它以JavaScript对象的形式存在,通过diff算法计算最小更新路径,从而减少对实际DOM的操作。
为什么需要虚拟DOM在现代前端开发中,虚拟DOM带来以下优势:
性能优化
批量DOM更新最小化DOM操作跨平台渲染服务端渲染开发体验
声明式编程组件化开发状态驱动UI代码可维护性跨平台能力
浏览器渲染原生应用渲染服务端渲染Canvas/WebGL渲染调试能力
状态追踪组件调试性能分析错误边界 核心实现 ⚡ 虚拟DOM节点定义 // vnode.ts export type VNodeType = string | Component; export interface VNode { type: VNodeType; props: Record<string, any>; children: (VNode | string)[]; key?: string | number; el?: HTMLElement | Text; } export interface Component { render: () => VNode; props?: Record<string, any>; setup?: (props: Record<string, any>) => Record<string, any>; } export function h( type: VNodeType, props: Record<string, any> = {}, children: (VNode | string)[] = [] ): VNode { return { type, props, children, key: props.key }; } // JSX类型定义 declare global { namespace JSX { interface Element extends VNode {} interface IntrinsicElements { [elemName: string]: any; } } } // 创建文本节点 export function createTextVNode(text: string): VNode { return { type: 'text', props: {}, children: [text] }; } // 创建Fragment export function Fragment(props: Record<string, any>): VNode { return { type: 'fragment', props, children: props.children || [] }; } DOM渲染实现 // renderer.ts export class Renderer { private container: HTMLElement; constructor(container: HTMLElement) { this.container = container; } render(vnode: VNode | null) { if (vnode === null) { // 卸载 if (this.container.firstChild) { this.container.innerHTML = ''; } return; } // 挂载或更新 const prevVNode = this.container._vnode; if (!prevVNode) { // 首次挂载 this.mount(vnode, this.container); } else { // 更新 this.patch(prevVNode, vnode, this.container); } this.container._vnode = vnode; } private mount(vnode: VNode, container: HTMLElement, anchor?: Node | null) { const { type, props, children } = vnode; if (typeof type === 'string') { // 创建元素 const el = document.createElement(type); vnode.el = el; // 设置属性 this.patchProps(el, {}, props); // 挂载子节点 children.forEach(child => { if (typeof child === 'string') { el.appendChild(document.createTextNode(child)); } else { this.mount(child, el); } }); // 插入到容器 container.insertBefore(el, anchor || null); } else if (typeof type === 'function') { // 挂载组件 this.mountComponent(vnode, container, anchor); } } private mountComponent( vnode: VNode, container: HTMLElement, anchor?: Node | null ) { const component = vnode.type as Component; // 执行setup let setupResult = {}; if (component.setup) { setupResult = component.setup(vnode.props); } // 执行render const renderVNode = component.render.call(setupResult); // 挂载渲染结果 this.mount(renderVNode, container, anchor); vnode.el = renderVNode.el; } private patch( n1: VNode, n2: VNode, container: HTMLElement, anchor?: Node | null ) { if (n1.type !== n2.type) { // 类型不同,直接替换 this.unmount(n1); this.mount(n2, container, anchor); return; } if (typeof n2.type === 'string') { // 更新元素 const el = (n2.el = n1.el as HTMLElement); // 更新属性 this.patchProps(el, n1.props, n2.props); // 更新子节点 this.patchChildren(n1, n2, el); } else if (typeof n2.type === 'function') { // 更新组件 this.patchComponent(n1, n2, container); } } private patchProps( el: HTMLElement, oldProps: Record<string, any>, newProps: Record<string, any> ) { // 移除旧属性 for (const key in oldProps) { if (!(key in newProps)) { if (key.startsWith('on')) { const event = key.slice(2).toLowerCase(); el.removeEventListener(event, oldProps[key]); } else { el.removeAttribute(key); } } } // 设置新属性 for (const key in newProps) { const newValue = newProps[key]; const oldValue = oldProps[key]; if (newValue !== oldValue) { if (key.startsWith('on')) { // 事件处理 const event = key.slice(2).toLowerCase(); if (oldValue) { el.removeEventListener(event, oldValue); } el.addEventListener(event, newValue); } else if (key === 'style') { // 样式处理 if (typeof newValue === 'string') { el.style.cssText = newValue; } else { for (const styleKey in newValue) { el.style[styleKey] = newValue[styleKey]; } } } else if (key === 'class') { // 类名处理 if (Array.isArray(newValue)) { el.className = newValue.join(' '); } else { el.className = newValue; } } else { // 其他属性 el.setAttribute(key, newValue); } } } } private patchChildren(n1: VNode, n2: VNode, container: HTMLElement) { const oldChildren = n1.children; const newChildren = n2.children; // 处理文本节点 if (typeof newChildren[0] === 'string') { if (typeof oldChildren[0] === 'string') { // 文本节点更新 if (newChildren[0] !== oldChildren[0]) { container.textContent = newChildren[0]; } } else { // 替换为文本节点 container.textContent = newChildren[0]; } return; } // 处理子节点数组 const oldLen = oldChildren.length; const newLen = newChildren.length; const commonLen = Math.min(oldLen, newLen); // 更新公共部分 for (let i = 0; i < commonLen; i++) { this.patch( oldChildren[i] as VNode, newChildren[i] as VNode, container ); } if (newLen > oldLen) { // 添加新节点 for (let i = commonLen; i < newLen; i++) { this.mount(newChildren[i] as VNode, container); } } else if (oldLen > newLen) { // 移除多余节点 for (let i = commonLen; i < oldLen; i++) { this.unmount(oldChildren[i] as VNode); } } } private unmount(vnode: VNode) { if (typeof vnode.type === 'string') { vnode.el?.parentNode?.removeChild(vnode.el); } else if (typeof vnode.type === 'function') { // 组件卸载 if (vnode.el) { vnode.el.parentNode?.removeChild(vnode.el); } } } } Diff算法实现 // diff.ts interface KeyToIndexMap { [key: string]: number; } export function patchKeyedChildren( oldChildren: VNode[], newChildren: VNode[], container: HTMLElement ) { let oldStartIdx = 0; let oldEndIdx = oldChildren.length - 1; let newStartIdx = 0; let newEndIdx = newChildren.length - 1; let oldStartVNode = oldChildren[oldStartIdx]; let oldEndVNode = oldChildren[oldEndIdx]; let newStartVNode = newChildren[newStartIdx]; let newEndVNode = newChildren[newEndIdx]; const keyToIndexMap: KeyToIndexMap = {}; while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (!oldStartVNode) { oldStartVNode = oldChildren[++oldStartIdx]; } else if (!oldEndVNode) { oldEndVNode = oldChildren[--oldEndIdx]; } else if (isSameVNode(oldStartVNode, newStartVNode)) { // 头部节点相同 patch(oldStartVNode, newStartVNode, container); oldStartVNode = oldChildren[++oldStartIdx]; newStartVNode = newChildren[++newStartIdx]; } else if (isSameVNode(oldEndVNode, newEndVNode)) { // 尾部节点相同 patch(oldEndVNode, newEndVNode, container); oldEndVNode = oldChildren[--oldEndIdx]; newEndVNode = newChildren[--newEndIdx]; } else if (isSameVNode(oldStartVNode, newEndVNode)) { // 老头和新尾相同 patch(oldStartVNode, newEndVNode, container); container.insertBefore( oldStartVNode.el!, oldEndVNode.el!.nextSibling ); oldStartVNode = oldChildren[++oldStartIdx]; newEndVNode = newChildren[--newEndIdx]; } else if (isSameVNode(oldEndVNode, newStartVNode)) { // 老尾和新头相同 patch(oldEndVNode, newStartVNode, container); container.insertBefore(oldEndVNode.el!, oldStartVNode.el!); oldEndVNode = oldChildren[--oldEndIdx]; newStartVNode = newChildren[++newStartIdx]; } else { // 处理其他情况 if (!keyToIndexMap) { // 生成旧节点的key映射 for (let i = oldStartIdx; i <= oldEndIdx; i++) { const key = oldChildren[i].key; if (key != null) { keyToIndexMap[key] = i; } } } // 在旧节点中寻找新头节点 const idxInOld = keyToIndexMap[newStartVNode.key!]; if (idxInOld === undefined) { // 新节点 mount(newStartVNode, container, oldStartVNode.el!); } else { // 移动节点 const vnodeToMove = oldChildren[idxInOld]; patch(vnodeToMove, newStartVNode, container); container.insertBefore(vnodeToMove.el!, oldStartVNode.el!); oldChildren[idxInOld] = undefined as any; } newStartVNode = newChildren[++newStartIdx]; } } // 处理剩余节点 if (oldStartIdx > oldEndIdx) { // 添加新节点 const anchor = newChildren[newEndIdx + 1] ? newChildren[newEndIdx + 1].el : null; for (let i = newStartIdx; i <= newEndIdx; i++) { mount(newChildren[i], container, anchor); } } else if (newStartIdx > newEndIdx) { // 移除多余节点 for (let i = oldStartIdx; i <= oldEndIdx; i++) { if (oldChildren[i]) { unmount(oldChildren[i]); } } } } function isSameVNode(n1: VNode, n2: VNode): boolean { return n1.type === n2.type && n1.key === n2.key; } 组件系统实现 🏗️ 组件定义 // component.ts export interface ComponentOptions { name?: string; props?: Record<string, PropOptions>; setup?: ( props: Record<string, any>, context: SetupContext ) => Record<string, any>; render?: () => VNode; } interface PropOptions { type: any; required?: boolean; default?: any; validator?: (value: any) => boolean; } interface SetupContext { attrs: Record<string, any>; slots: Record<string, (...args: any[]) => VNode[]>; emit: (event: string, ...args: any[]) => void; } export function defineComponent(options: ComponentOptions) { return { name: options.name, props: options.props, setup: options.setup, render: options.render, // 组件实例创建 create(props: Record<string, any>) { // 创建组件实例 const instance = { props: shallowReactive(props), attrs: {}, slots: {}, emit: (event: string, ...args: any[]) => { const handler = props[`on${capitalize(event)}`]; if (handler) { handler(...args); } } }; // 执行setup if (options.setup) { const setupContext = { attrs: instance.attrs, slots: instance.slots, emit: instance.emit }; const setupResult = options.setup( instance.props, setupContext ); if (typeof setupResult === 'function') { // setup返回渲染函数 instance.render = setupResult; } else if (typeof setupResult === 'object') { // setup返回状态对象 instance.setupState = proxyRefs(setupResult); } } // 渲染函数 instance.render = options.render || instance.render; return instance; } }; } // 工具函数 function capitalize(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); } 生命周期实现 // lifecycle.ts export const enum LifecycleHooks { BEFORE_CREATE = 'beforeCreate', CREATED = 'created', BEFORE_MOUNT = 'beforeMount', MOUNTED = 'mounted', BEFORE_UPDATE = 'beforeUpdate', UPDATED = 'updated', BEFORE_UNMOUNT = 'beforeUnmount', UNMOUNTED = 'unmounted' } export function injectHook( type: LifecycleHooks, hook: Function, target: any ): Function | undefined { if (target) { const hooks = target[type] || (target[type] = []); const wrappedHook = (...args: any[]) => { hook.call(target, ...args); }; hooks.push(wrappedHook); return wrappedHook; } } export const createHook = (lifecycle: LifecycleHooks) => { return (hook: Function, target: any = currentInstance) => injectHook(lifecycle, hook, target); }; export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT); export const onMounted = createHook(LifecycleHooks.MOUNTED); export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE); export const onUpdated = createHook(LifecycleHooks.UPDATED); export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT); export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED); 性能优化 ⚡ 静态节点优化 // optimize.ts interface StaticNodeAnalysis { isStatic: boolean; staticRoot: boolean; } export function optimizeNode(node: VNode): StaticNodeAnalysis { if (typeof node.type === 'string') { // 分析静态节点 const analysis: StaticNodeAnalysis = { isStatic: true, staticRoot: false }; // 检查属性 for (const key in node.props) { if ( key === 'v-if' || key === 'v-for' || key === 'v-model' || key.startsWith(':') || key.startsWith('@') ) { analysis.isStatic = false; break; } } // 检查子节点 if (analysis.isStatic && node.children.length > 0) { let allChildrenStatic = true; let staticChildCount = 0; for (const child of node.children) { if (typeof child === 'object') { const childAnalysis = optimizeNode(child); if (!childAnalysis.isStatic) { allChildrenStatic = false; break; } if (childAnalysis.staticRoot) { staticChildCount++; } } } analysis.staticRoot = allChildrenStatic && staticChildCount > 0; } return analysis; } return { isStatic: false, staticRoot: false }; } // 标记静态根节点 export function markStaticRoots(node: VNode, analysis: StaticNodeAnalysis) { if (analysis.staticRoot) { node.staticRoot = true; // 缓存静态子树 node.staticChildren = [...node.children]; } // 递归处理子节点 for (const child of node.children) { if (typeof child === 'object') { const childAnalysis = optimizeNode(child); markStaticRoots(child, childAnalysis); } } } 更新优化 // update-optimization.ts export class UpdateOptimizer { private static readonly BATCH_SIZE = 1000; private updates: Set<VNode> = new Set(); private updating = false; queueUpdate(vnode: VNode) { this.updates.add(vnode); if (!this.updating) { this.updating = true; requestAnimationFrame(() => this.processUpdates()); } } private processUpdates() { const updates = Array.from(this.updates); this.updates.clear(); this.updating = false; // 批量处理更新 for (let i = 0; i < updates.length; i += this.BATCH_SIZE) { const batch = updates.slice(i, i + this.BATCH_SIZE); this.processBatch(batch); } } private processBatch(vnodes: VNode[]) { // 按照组件层级排序 vnodes.sort((a, b) => getDepth(a) - getDepth(b)); // 合并同层级更新 const updateMap = new Map<number, VNode[]>(); for (const vnode of vnodes) { const depth = getDepth(vnode); if (!updateMap.has(depth)) { updateMap.set(depth, []); } updateMap.get(depth)!.push(vnode); } // 按层级处理更新 for (const [depth, nodes] of updateMap) { this.processDepthUpdates(nodes); } } private processDepthUpdates(vnodes: VNode[]) { // 处理同一层级的更新 for (const vnode of vnodes) { if (vnode.staticRoot) { // 跳过静态根节点 continue; } // 更新节点 patch(vnode, vnode, vnode.el!.parentNode); } } } function getDepth(vnode: VNode): number { let depth = 0; let current = vnode; while (current.parent) { depth++; current = current.parent; } return depth; } 最佳实践建议 ⭐ 性能优化建议节点优化
使用key标识提取静态节点避免深层嵌套合理使用v-show更新优化
批量更新异步更新合并操作缓存结果渲染优化
懒加载组件虚拟滚动时间切片优先级调度 开发建议 组件设计 // 好的实践 const GoodComponent = defineComponent({ name: 'GoodComponent', props: { items: { type: Array, required: true } }, setup(props) { // 提取复杂逻辑 const state = reactive({ selectedIndex: -1 }); // 计算属性 const filteredItems = computed(() => props.items.filter(item => item.visible) ); return { state, filteredItems }; } }); // 避免这样做 const BadComponent = defineComponent({ render() { // 渲染函数中包含复杂逻辑 const items = this.items.filter(item => { return item.visible && this plexCheck(item); }); return h('div', {}, items.map(item => h('div', { key: item.id }, item.name) )); } }); 更新处理 // 好的实践 function handleUpdates() { // 批量更新 nextTick(() => { state.count++; state.total = calculateTotal(); }); } // 避免频繁更新 function badUpdate() { state.count++; state.total = calculateTotal(); // 直接触发DOM更新 } 结语 📝虚拟DOM是现代前端框架的重要基石,通过本文,我们学习了:
虚拟DOM的核心概念DOM diff算法的实现组件系统的设计性能优化的策略开发中的最佳实践💡 学习建议:
深入理解虚拟DOM原理掌握diff算法的优化注重性能优化实践遵循最佳实践指南持续学习新的优化方案如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻
工程化与框架系列(13)--虚拟DOM实现由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“工程化与框架系列(13)--虚拟DOM实现”
上一篇
linux学习笔记3
下一篇
前端Npm面试题及参考答案