当前位置: 首页 > news >正文

工程化与框架系列(13)--虚拟DOM实现

虚拟DOM实现 🌳

虚拟DOM(Virtual DOM)是现代前端框架的核心技术之一,它通过在内存中维护UI的虚拟表示来提高渲染性能。本文将深入探讨虚拟DOM的实现原理和关键技术。

虚拟DOM概述 🌟

💡 小知识:虚拟DOM是对真实DOM的一种轻量级抽象表示,它以JavaScript对象的形式存在,通过diff算法计算最小更新路径,从而减少对实际DOM的操作。

为什么需要虚拟DOM

在现代前端开发中,虚拟DOM带来以下优势:

  1. 性能优化

    • 批量DOM更新
    • 最小化DOM操作
    • 跨平台渲染
    • 服务端渲染
  2. 开发体验

    • 声明式编程
    • 组件化开发
    • 状态驱动UI
    • 代码可维护性
  3. 跨平台能力

    • 浏览器渲染
    • 原生应用渲染
    • 服务端渲染
    • Canvas/WebGL渲染
  4. 调试能力

    • 状态追踪
    • 组件调试
    • 性能分析
    • 错误边界

核心实现 ⚡

虚拟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;
}

最佳实践建议 ⭐

性能优化建议

  1. 节点优化

    • 使用key标识
    • 提取静态节点
    • 避免深层嵌套
    • 合理使用v-show
  2. 更新优化

    • 批量更新
    • 异步更新
    • 合并操作
    • 缓存结果
  3. 渲染优化

    • 懒加载组件
    • 虚拟滚动
    • 时间切片
    • 优先级调度

开发建议

  1. 组件设计
// 好的实践
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.complexCheck(item);
        });

        return h('div', {}, items.map(item =>
            h('div', { key: item.id }, item.name)
        ));
    }
});
  1. 更新处理
// 好的实践
function handleUpdates() {
    // 批量更新
    nextTick(() => {
        state.count++;
        state.total = calculateTotal();
    });
}

// 避免频繁更新
function badUpdate() {
    state.count++;
    state.total = calculateTotal();
    // 直接触发DOM更新
}

结语 📝

虚拟DOM是现代前端框架的重要基石,通过本文,我们学习了:

  1. 虚拟DOM的核心概念
  2. DOM diff算法的实现
  3. 组件系统的设计
  4. 性能优化的策略
  5. 开发中的最佳实践

💡 学习建议:

  1. 深入理解虚拟DOM原理
  2. 掌握diff算法的优化
  3. 注重性能优化实践
  4. 遵循最佳实践指南
  5. 持续学习新的优化方案

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关文章:

  • Springboot中SLF4J详解
  • Winbox5怎样设置上网
  • SpringMVC(2)传递JSON、 从url中获取参数、上传文件、cookie 、session
  • 【图文详解】什么是微服务?什么是SpringCloud?
  • Python 实现定时查询数据库并发送消息的完整流程
  • Eureka Server 数据同步原理深度解析
  • Go红队开发—编解码工具
  • 2025年02月26日Github流行趋势
  • C++之vector
  • 如何在工控机上实现机器视觉检测?
  • Vue05
  • 计算机毕业设计SpringBoot+Vue.js英语知识应用网站(源码+文档+PPT+讲解)
  • 如何下载MinGW-w64到MATLAB
  • 解决Docker Desktop启动后Docker Engine stopped问题
  • 进入DeepSeek部署第一阵营后,奇墨科技推进多元应用场景落地
  • 小红的回文子串
  • CSS 实现波浪效果
  • Ubuntu 下 nginx-1.24.0 源码分析 - ngx_modules
  • 前端Npm面试题及参考答案
  • 深度剖析数据分析职业成长阶梯
  • 炫酷html5网站模板/青岛自动seo
  • 怎么做独立app网站/宁德市人民医院
  • wordpress网站慢/按效果付费的网络推广方式
  • 营销型网站建设价格/济南百度代理
  • wordpress插件外贸/泰州网站建设优化
  • 能源建设网站/网站内容管理系统