Vue渲染器解析

渲染器是 Vue 与浏览器之间的「翻译官」。它拿到一份用 JavaScript 对象描述的 UI(虚拟 DOM),然后精准地创建、更新、销毁真实 DOM,同时把响应式数据和渲染函数绑定成一条自动刷新的流水线。

一、核心职责:挂载 + 更新 + 卸载

渲染器的使命只有三句话:

  • 首次出现时把虚拟节点挂载成真实节点;
  • 数据变化时用最小代价更新节点;
  • 节点消失时把 DOM 和副作用清理干净。

所有细节都围绕这三件事展开。

二、从代码看挂载全流程

js 复制代码
function mountElement(vnode, container) {
  // 1. 创建元素
  const el = document.createElement(vnode.type);

  // 2. 处理属性
  if (vnode.props) {
    for (const key in vnode.props) {
      const value = vnode.props[key];
      if (key.startsWith('on')) {
        // 事件
        el.addEventListener(key.slice(2).toLowerCase(), value);
      } else if (key === 'class') {
        // class 归一化后一次性赋值
        el.className = normalizeClass(value);
      } else {
        // 普通属性
        el[key] = value;
      }
    }
  }

  // 3. 处理子节点
  if (typeof vnode.children === 'string') {
    el.textContent = vnode.children;
  } else if (Array.isArray(vnode.children)) {
    vnode.children.forEach(child => mountElement(child, el));
  }

  // 4. 插入文档
  container.appendChild(el);
}

示例代码已经覆盖「元素创建、属性绑定、事件监听、子节点递归、DOM 插入」五大动作。真实 Vue 只是在此基础上加了 patchFlag、Block Tree 等优化,逻辑完全一致。

三、元素挂载处理细节问题

1. 属性到底用 setAttribute 还是 elkey

普通字符串属性推荐 el[key],少一次字符串解析,性能更优。布尔属性如 disabled 必须 el.disabled = false,否则 setAttribute('disabled', 'false') 会把按钮禁用。只读属性如 form 只能 setAttribute,因为 el.form 是只读。

Vue 内部用 shouldSetAsProps 函数做决策:

js 复制代码
function shouldSetAsProps(el, key) {
  if (key === 'form' && el.tagName === 'INPUT') return false;
  return key in el;
}

先判断是否有对应 DOM 属性,再决定走哪条路,确保正确性与性能兼得。

2.class 的特殊处理:字符串、对象、数组一网打尽

模板中的 :class 可能是字符串、对象或数组,渲染器先用 normalizeClass 统一成空格分隔的字符串,再一次性赋给 el.className,避免多次 DOM 操作。

js 复制代码
function isString(value) {
  return typeof value === "string";
}

function isArray(value) {
  return Array.isArray(value);
}

function isObject(value) {
  return value !== null && typeof value === "object";
}

function normalizeClass(value) {
  let res = "";
  if (isString(value)) {
    res = value;
  } else if (isArray(value)) {
    // 如果是数组,递归调用 normalizeClass
    for (let i = 0; i < value.length; i++) {
      const normalized = normalizeClass(value[i]);
      if (normalized) {
        res += (res ? " " : "") + normalized;
      }
    }
  } else if (isObject(value)) {
    // 如果是对象,则检查每个 key 是否为真值
    for (const name in value) {
      if (value[name]) {
        res += (res ? " " : "") + name;
      }
    }
  }
  return res;
}
normalizeClass(['foo', { bar: true, baz: false }]) // → 'foo bar'

3.子节点的挂载

子节点可能是文本、数组或自定义组件。

  • 文本直接 textContent
  • 数组递归 mountElement
  • 组件则执行 mountComponent,组件再返回新的虚拟节点,继续递归。

整个页面就是一颗虚拟 DOM 树 深度优先地展开成真实 DOM 树。

js 复制代码
function mountElement(vnode, container) {
  const el = createElement(vnode.type);
  
  // 针对子节点进行处理
  if (typeof vnode.children === "string") {
    // 如果 children 是字符串,则直接将字符串插入到元素中
    setElementText(el, vnode.children);
  } else if (Array.isArray(vnode.children)) {
    // 如果 children 是数组,则遍历每一个子节点,并调用 patch 函数挂载它们
    vnode.children.forEach((child) => {
      patch(null, child, el);
    });
  }
  insert(el, container);
}
相关推荐
小江的记录本44 分钟前
【JVM虚拟机】类加载机制:类加载全流程:加载→验证→准备→解析→初始化(附《思维导图》+《面试高频考点清单》)
java·jvm·spring boot·算法·安全·spring·面试
Larcher1 小时前
JS 变量提升:代码没动,为什么执行顺序就变了?
前端·javascript·前端框架
yingyima1 小时前
MySQL 事件调度器速查:核心语法与实战代码
前端
GISer_Jing1 小时前
Claude Code多Agent架构深度剖析
前端·人工智能·架构·自动化
comphub1 小时前
comp-hub:让你的 Vue 业务组件真正"活"起来
前端
AI砖家1 小时前
Claude Code 跳过确认完全指南:让 AI 自己完成开发任务
前端·人工智能·python·ai编程·代码规范
西安邮电大学1 小时前
Redis为什么快?
java·redis·后端·其他·面试
折哥的程序人生 · 物流技术专研2 小时前
《Java 100 天进阶之路》第39篇:Java泛型方法的定义和使用
java·开发语言·后端·面试·求职招聘
KaMeidebaby2 小时前
卡梅德生物技术快报|Pull Down 实验在 lncRNA - 蛋白互作机制研究中的应用实例解析
大数据·前端·架构·spark·新浪微博
锋行天下2 小时前
让nginx网关扛下所有攻击
前端·后端·nginx