vue3运行时核心模块之runtime-dom

runtime-dom 是Vue 3 源码中一个核心的运行时包,主要作用是为Vue应用提供与浏览器DOM 相关的具体实现,包括节点元素操作,监听事件绑定,属性更新等与DOM元素相关的一系列方法。

简单来说:runtime-dom 是 Vue 的"DOM 操作适配层"

一、为什么需要 runtime-dom

Vue 是与平台无关的框架,这也意味着同一套核心逻辑,可以运行在常见的有浏览器(DOM)、Nuxt.js(SSR)、Weex(Native APP)等环境中。

在源码中所在的位置:

二、runtime-dom的核心功能

1.DOM操作之nodeOps

javascript 复制代码
// 导出 nodeOps 对象,它实现了 Renderer(渲染器)所需的平台相关 DOM 操作方法
// 使用 Omit 排除了 'patchProp' 方法(该方法由其他模块单独实现)
export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
  // 插入子节点到父节点中,anchor 为插入位置的参考节点(插入到 anchor 之前)
  insert: (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null)
  },

  // 从 DOM 中移除指定子节点
  remove: child => {
    const parent = child.parentNode
    if (parent) {
      parent.removeChild(child)
    }
  },

  // 创建元素节点
  // 参数说明:
  // - tag: 元素标签名
  // - namespace: 命名空间(如 'svg'、'mathml' 或 undefined)
  // - is: 自定义元素的 is 属性(用于 Web Components)
  // - props: 初始化属性(目前仅用于 select 的 multiple 属性)
  createElement: (tag, namespace, is, props): Element => {
    let el: Element

    // 根据命名空间使用不同的 createElementNS 或 createElement
    if (namespace === 'svg') {
      el = doc.createElementNS(svgNS, tag) // SVG 命名空间
    } else if (namespace === 'mathml') {
      el = doc.createElementNS(mathmlNS, tag) // MathML 命名空间
    } else if (is) {
      el = doc.createElement(tag, { is }) // 自定义元素(Web Components)
    } else {
      el = doc.createElement(tag) // 普通 HTML 元素
    }

    // 特殊处理 <select multiple>:某些浏览器需要显式设置 multiple 属性
    if (tag === 'select' && props && props.multiple != null) {
      (el as HTMLSelectElement).setAttribute('multiple', props.multiple)
    }

    return el
  },

  // 创建文本节点
  createText: text => doc.createTextNode(text),

  // 创建注释节点
  createComment: text => doc.createComment(text),

  // 设置文本节点的内容(nodeValue)
  setText: (node, text) => {
    node.nodeValue = text
  },

  // 设置元素的文本内容(textContent)
  setElementText: (el, text) => {
    el.textContent = text
  },

  // 获取节点的父节点(类型断言为 Element | null)
  parentNode: node => node.parentNode as Element | null,

  // 获取节点的下一个兄弟节点
  nextSibling: node => node.nextSibling,

  // 查询选择器(document.querySelector)
  querySelector: selector => doc.querySelector(selector),

  // 设置作用域 ID(用于 scoped CSS)
  // 实际上是给元素添加一个空属性,例如 <div data-v-xxx="">
  setScopeId(el, id) {
    el.setAttribute(id, '')
  },

  // __UNSAFE__(不安全操作)
  // 原因:使用了 innerHTML。
  // 安全前提:静态内容仅来自编译后的模板(用户只使用可信模板时是安全的)
  // 功能:批量插入静态 HTML 内容(用于优化静态节点的渲染性能)
  insertStaticContent(content, parent, anchor, namespace, start, end) {
    // 记录插入前的最后一个子节点(用于后续返回插入范围)
    const before = anchor ? anchor.previousSibling : parent.lastChild

    // 如果提供了缓存的起始/结束节点(start/end),且结构有效,则复用克隆方式插入(性能优化)
    if (start && (start === end || start.nextSibling)) {
      // 缓存路径:克隆已存在的 DOM 节点
      while (true) {
        parent.insertBefore(start!.cloneNode(true), anchor)
        if (start === end || !(start = start!.nextSibling)) break
      }
    } else {
      // 非缓存路径:通过 innerHTML 解析新内容
      // 根据命名空间包裹内容(确保正确解析 SVG/MathML)
      templateContainer.innerHTML = unsafeToTrustedHTML(
        namespace === 'svg'
          ? `<svg>${content}</svg>`
          : namespace === 'mathml'
            ? `<math>${content}</math>`
            : content,
      ) as string

      const template = templateContainer.content // DocumentFragment

      // 如果是 SVG 或 MathML,移除外层包裹标签(因为 innerHTML 需要根元素)
      if (namespace === 'svg' || namespace === 'mathml') {
        const wrapper = template.firstChild!
        while (wrapper.firstChild) {
          template.appendChild(wrapper.firstChild) // 提取内部节点
        }
        template.removeChild(wrapper) // 移除 <svg> 或 <math> 包裹
      }

      // 将解析好的 DocumentFragment 插入到目标位置
      parent.insertBefore(template, anchor)
    }

    // 返回插入内容的第一个和最后一个节点,供后续引用或缓存
    return [
      // 第一个插入的节点
      before ? before.nextSibling! : parent.firstChild!,
      // 最后一个插入的节点
      anchor ? anchor.previousSibling! : parent.lastChild!,
    ]
  },
}

2.属性操作之patchProp

javascript 复制代码
/**
 * 判断属性名是否为事件处理器(以 on 开头且后接大写字母)
 * @param key 要检测的属性名
 * @returns 布尔值:是否为事件属性
 */
export const isOn = (key) => {
  // 核心逻辑:
  // 1. 字符串长度至少为 3(on + 至少一个大写字母)
  // 2. 前两个字符是 'on'
  // 3. 第三个字符是大写字母(A-Z)
  return key.length > 2 && key[0] === 'o' && key[1] === 'n' && /[A-Z]/.test(key[2])
}
export const patchClass = (el,value)=>{
    if(value == null){
        value = '';
    }
    el.className = value;// 设置样式名
}
export const patchStyle = (el, prev, next) => {
    const style = el.style;
    if (!next) {
        el.removeAttribute('style');
    } else {

        for (const key in next) {
            style[key] = next[key];
        }
        if (prev) {
            for (const key in prev) {
                if (next[key] == null) {
                    style[key] = '';
                }
            }
        }
    }
}
export const patchEvent = (el, rawName, nextValue) => {
    const invokers = el._vei || (el._vei = {});
    const exisitingInvoker = invokers[rawName];
    if (nextValue && exisitingInvoker) { // 如果绑定过,则替换为新的
        exisitingInvoker.value = nextValue;
    } else {
        const name = rawName.slice(2).toLowerCase();
        if (nextValue) {  // 绑定新值
            const invoker = (invokers[rawName] = createInvoker(nextValue));
            el.addEventListener(name, invoker);
        } else if (exisitingInvoker) {
            el.removeEventListener(name, exisitingInvoker);
            invokers[rawName] = undefined;
        }
    }
}
function createInvoker(initialValue) {
    const invoker = (e) => {
        invoker.value(e);
    }
    invoker.value = initialValue;
    return invoker;
}
export const patchAttr = (el, key, value) => {
    if (value == null) {
        el.removeAttribute(key);
    } else {
        el.setAttribute(key, value);
    }
}
function patchProp(el, key, preValue, nextValue) {
    switch (key) {
        case 'class':
                patchClass(el, nextValue);
            break;
        case 'style':
            patchStyle(el, preValue, nextValue);
            break;
        default:
            if (isOn(key)) { // 事件如onClick
                patchEvent(el, key, nextValue);
            } else {
                patchAttr(el, key, nextValue);
            }
            break;
    }
}

以上方法会被传给 runtime-core,由核心模块调用,runtime-core 使用这套接口操作对应平台环境,而无需知道底层是 DOM 还是其他。对于runtime-core,后续会再单独整理成对应文章。

三、总结

runtime-dom是Vue 3 在浏览器环境中的 DOM 操作实现层,提供 createElement, patchProp, insert 等 DOM 操作。目的是解耦核心逻辑与平台细节,支持多端渲染,通过与runtime-core结合使用,把虚拟 DOM 变成真实页面,这也是 runtime-dom主要职责之一。今天简单总结了 runtime-dom的部分核心实现,如果有问题的小伙伴欢迎在评论区沟通交流!

相关推荐
一只大侠的侠2 小时前
React Native for OpenHarmony:日期选择功能完整实现指南
javascript·react native·react.js
henry1010102 小时前
DeepSeek生成的网页版念经小助手
javascript·css·html5·工具
一只大侠的侠2 小时前
React Native实战:高性能StickyHeader粘性标题组件实现
javascript·react native·react.js
夏幻灵2 小时前
CSS 布局深究:行框模型、幽灵节点与绝对居中的数学原理
前端·css
打瞌睡的朱尤2 小时前
Vue day13~16Vue特性,Pinia,大事件项目
前端·javascript·vue.js
We་ct2 小时前
LeetCode 61. 旋转链表:题解+思路拆解
前端·算法·leetcode·链表·typescript
风象南2 小时前
用 ASCII 草图 + AI 快速生成前端代码
前端
大数据基础2 小时前
基于 Hadoop MapReduce + Spring Boot + Vue 3 的每日饮水数据分析平台
大数据·vue.js·hadoop·spring boot·数据分析·maven·mapreduce
_OP_CHEN2 小时前
【前端开发之JavaScript】(三)JS基础语法中篇:运算符 / 条件 / 循环 / 数组一网打尽
开发语言·前端·javascript·网页开发·图形化界面·语法基础·gui开发