众所周知,直接操作DOM是一个昂贵的操作,会销毁所有DOM元素,再全量创建新的DOM元素。
而虚拟DOM的意义就是找出差异的性能消耗最小化,通过减少直接的DOM操作来提高性能,而不是重新渲染整个页面。
渲染器的作用就是递归地遍历虚拟DOM对象,并调用原生DOM API来完成真实DOM的创建,将虚拟DOM转化为真实DOM。
假设我们有如下虚拟DOM:
const vnode = {
tag: 'div',
props: {
onClick: () => console.log('hello!')
},
children: 'click me'
}
接下来,我们需要编写一个渲染器,把上面这段虚拟DOM渲染为真实DOM。
function renderer(vnode, container) {
// 使用 vnode.tag 作为标签名称创建 DOM 元素
const el = document.createElement(vnode.tag);
// 遍历 vnode.props,将属性、事件添加到 DOM 元素
for (const key in vnode.props) {
if (/^on/.test(key)) {
// 如果 key 以 on 开头,说明它是事件
el.addEventListener(
key.substr(2).toLowerCase(), // 事件名称 onClick --> click
vnode.props[key] // 事件处理函数
);
}
}
// 处理 children
if (typeof vnode.children === 'string') {
// 如果 children 是字符串,说明它是元素的文本子节点
el.appendChild(document.createTextNode(vnode.children));
} else if (Array.isArray(vnode.children)) {
// 递归地调用 renderer 函数渲染子节点,使用当前元素 el作为挂载点
vnode.children.forEach(child => renderer(child, el));
}
// 将元素添加到挂载点下
container.appendChild(el);
}
这里的 renderer 函数接收如下两个参数:
总体来说,实现一个简单的渲染器分为三步:
当然,vnode.tag 也可以是组件,组件的本质就是一组DOM元素的封装。
在生成的虚拟DOM对象中多出了一个 patchFlags 属性,我们假设数字1代表“class是动态的”,这样渲染器看到这个标志时就知道:“哦,原来只有class属性会发生改变。”
它会通过Diff算法找出变更点,并且只会更新需要更新的内容。
就相当于省去了寻找变更点的工作量,性能自然就提升了。通过这篇文章,我们了解了虚拟DOM和渲染器的基本概念,以及如何实现一个简单的渲染器。

