简单总结一下,由于在浏览器端频繁操作 DOM 是非常耗性能的事情,为了避免这种情况,我们会使用 JS 来模拟 DOM 结构,同时,DOM 结构的变化也同样放在 JS 层操作(JS 是图灵完备语言),这就是为什么会存在 virtual DOM 的原因。 既然 DOM 结构需要用 JS 来进行模拟,那我们下面就举一个具体的例子看看究竟是如何进行模拟的呢?
<scriptsrc="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom.js"></script> <scriptsrc="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-class.js"></script> <scriptsrc="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-props.js"></script> <scriptsrc="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-style.js"></script> <scriptsrc="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-eventlisteners.js"></script> <scriptsrc="https://cdn.bootcss.com/snabbdom/0.7.0/h.js"></script> <scripttype="text/javascript"> var snabbdom = window.snabbdom // 定义关键函数 patch var patch = snabbdom.init([ snabbdom_class, snabbdom_props, snabbdom_style, snabbdom_eventlisteners ]) // 定义关键函数 h var h = snabbdom.h // 原始数据 var data = [ { name: '张三', age: '20', address: '北京' }, { name: '李四', age: '21', address: '上海' }, { name: '王五', age: '22', address: '广州' } ] // 把表头也放在 data 中 data.unshift({ name: '姓名', age: '年龄', address: '地址' }) var container = document.getElementById('container') // 渲染函数 var vnode functionrender(data) { var newVnode = h('table', {}, data.map(function (item) { var tds = [] var i for (i in item) { if (item.hasOwnProperty(i)) { tds.push(h('td', {}, item[i] + '')) } } returnh('tr', {}, tds) })) if (vnode) { // re-render patch(vnode, newVnode) } else { // 初次渲染 patch(container, newVnode) } // 存储当前的 vnode 结果 vnode = newVnode } // 初次渲染 render(data) var btnChange = document.getElementById('btn-change') btnChange.addEventListener('click', function () { data[1].age = 30 data[2].address = '深圳' // re-render render(data) }) </script> </body> </html>
上面的实现过程基本与 jQuery 的相类似,只不过引用了 snabbdom 中的函数,大家可以去浏览器中观察 DOM 的变化,看看与之前的有什么不同,不同的地方也恰恰就是 virtual DOM 存在的原因。 讲了 virtual DOM 是什么,也初步体验了 virtual DOM 的实现过程,接下来,我们继续去了解 virtual DOM 中的核心算法 —— diff 算法。
// patch(container, vnode); functioncreateElement(vnode) { var tag = vnode.tag// 'ul' var attrs = vnode.attrs || {} var children = vnode.children || [] if (!tag) { returnnull }
// 创建真实的 DOM 元素 var elem = document.createElement(tag) // 属性 var attrName for (attrName in attrs) { if (attrs.hasOwnProperty(attrName)) { // 给 elem 添加属性 elem.setAttribute(attrName, attrs[attrName]) } } // 子元素 children.forEach(function (childVnode) { // 给 elem 添加子元素 elem.appendChild(createElement(childVnode)) // 递归 })
functionreplaceNode(vnode, newVnode) { var elem = vnode.elem// 真实的 DOM 节点 var newElem = createElement(newVnode)
// 替换 }
上面的 createElement、updateChildren 仅仅是对 DOM 元素做了最简单的对比,就像本节开始提醒到的,我们现在在了解的阶段,无需去关注细节,把握大体实现流程即可。本文没有涉及到的内容,比如节点的新增和删除、节点的重新排序、节点的样式、属性、事件绑定等内容,如果有兴趣的同学可以自己下来慢慢研究。
以上就是 virtual DOM 与 diff 算法入门介绍的全部内容了,我们从为什么会有 virtual DOM 入手,介绍了它是什么以及如何应用,同时介绍了最核心的 diff 算法,希望对大家有所帮助。