如何实现Virtual DOM

mac2026-06-13  0

前言

前端开发者都知道,在网页中浏览器资源开销最大的便是DOM节点渲染了,DOM很慢并且非常庞大,网页性能问题大多数都是由JavaScript修改DOM所引起的,网页的重排、重绘都非常的消耗浏览器性能。重排、重绘的次数越多,我们的应用程序就会越卡顿。

DOM结构可以看作是从根节点开始的一个树结构,那么我们就可以通过JavaScript的对象来描述一个DOM,这就是虚拟DOM,JavaScript运行速度很快,虚拟DOM是放在JS 和 HTML中间的一个层。它可以通过新旧DOM的对比,来获取对比之后的差异对象,然后有针对性的把差异部分真正地渲染到页面上,从而减少实际DOM操作,最终达到性能优化的目的。

虚拟dom原理流程

简单概括有三点:

用JavaScript模拟DOM树,并渲染这个DOM树比较新老DOM树,得到比较的差异对象把差异对象应用到渲染的DOM树。

流程如下图所示:

本篇文章主要讲述的是如何用JavaScript模拟DOM树,并渲染这个DOM树!

用JavaScript模拟DOM树并渲染到页面上

我们用JavaScript可以很容易的模拟一个DOM树的结构,例如用这样的一个构造函数createEl(tagName, props, children)来创建一个DOM结构。

tagName:标签名props:标签上的属性children:子节点

那么我们就以类的方式来创建吧,创建一个类,模拟vue的方式,基于一个根标签构建整个dom树:

class CreateEl { constructor(tagName, props, children) { // 当只有两个参数tagName和children的时候,例如 celement(el, [123]) 做容错处理 if (Array.isArray(props)) { children = props props = {} } // tagName, props, children数据保存到this实例化对象上 this.tagName = tagName this.props = props || {} this.children = children || [] this.key = props ? props.key : undefined // 对于Vue的v-for,react的map循环,加key可以提高diff效率 // 定义count在diff算法使用 let count = 0 this.children.forEach(child => { if (child instanceof CreateEl) { count += child.count } else { child = '' + child } count++ }) // 给每一个节点设置一个count this.count = count } }

这样我们就创建好了一个创建DOM元素的类,实例化的对象中有传入的标签名、标签绑定的属性及子属性。下面我们需要将这个实例化的对象转化为一个真实的DOM元素,所以我们需要给CreateEl类的原型上加一个 render 方法,代码定义如下:

// 构建一个 dom 树 CreateEl.prototype.render = function () { // 创建dom const el = document.createElement(this.tagName) const props = this.props // 循环所有属性,然后设置属性 for (let [key, val] of Object.entries(props)) { el.setAttribute(key, val) } this.children.forEach(child => { // 递归循环 构建dom tree,递归的终止条件:子节点为文本节点 let childEl = (child instanceof CreateEl) ? child.render() : document.createTextNode(child) el.appendChild(childEl) }) return el }

render方法最终会返回一个完整的真实DOM Tree,它能直接通过 document.body.appendChild() 渲染到页面上。我们再对CreateEl类进行一次封装:

let h = (tagName, props, children) => { return new CreateEl(tagName, props, children) }

这个时候虚拟DOM就实现完成了,我们可以以JavaScript对象形式给标签绑定各种属性:style、event、class、id。下面我们就可以来试一试:

let a = h('ul', { id: 'ol-list' }, [ h('li', { class: 'item1', style: "list-style: none" }, ['Item1']), h('li', { class: 'item2', id: 'li2', style: "color: pink", onclick: 'printStr ()' }, ['Item2']), h('li', { class: 'item3', onclick: 'alert(1234)' }, ['Item3']) ]).render() // 供事件绑定的方法 function printStr() { alert('虚拟dom') } // 将DOM元素渲染到浏览器上 document.body.appendChild(a)

最后的结果就如下所示:

 点击事件也能够生效,到了这里觉得原生JS真的有太多的可玩性了~~

最新回复(0)