Vue渲染器解析

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

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

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

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

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

二、从代码看挂载全流程


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 还是 el[key]?

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

Vue 内部用 shouldSetAsProps 函数做决策:


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

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

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

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


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 树。


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); }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值