前言
Vue
和React
的Render
函数中都波及到了Virtual DOM
的概念,Virtual DOM
也是性能优化上的重要一环,同时冲破了间接操作实在DOM
的瓶颈,本文带着以下几个问题来论述Virtual DOM
。
- 1.为什么要操作虚构 DOM?
- 2.什么是虚构 DOM?
- 3.手把手教你实现虚构 DOM 渲染实在 DOM
心愿浏览本文之后,可能让你深刻的理解虚构 DOM
并且在开发和面试中收益。
为什么要操作虚构 DOM
为了帮忙咱们更好的了解为什么要操作虚构 DOM,咱们先从浏览器渲染一个 HTML 文件须要做哪些事件说起:
浏览器渲染机制大抵能够分为以下 5 步走:
- 1.创立 DOM tree
- 2.创立 Style Rules
- 3.构建 Render tree
- 4.布局 Layout
- 5.绘制 Painting
咱们过来应用原生JavaScript
和jquery
去操作实在DOM
的时候,浏览器会从构建 DOM???? 开始从头到尾的执行一遍渲染的流程。
在一次开发中,如果产品通知你一个需要,你须要在一次操作中更新10个DOM
节点,现实状态是浏览器一次性构建完DOM树
,再执行后续操作。但浏览器没这么智能,收到第一个更新 DOM 申请后,并不知道后续还有9次更新操作
,因而会马上执行流程,最终执行 10 次流程。
过了一会产品经理把你叫过来和你说把需要改一下,此时你又须要操作一次 DOM 的更新,那么这个时候之前做的 10 次 DOM 操作就是白白节约性能
,节约感情。
即便计算机硬件始终在更新迭代,然而操作DOM
的代价仍旧是低廉的,频繁操作 DOM 还是会呈现页面卡顿
,影响用户的体验。实在的 DOM 节点,哪怕一个最简略的 div 也蕴含着很多属性,能够打印进去直观感受一下:
如此多的属性,如果每次对 DOM 构造都进行更新,一次,两次,三次...一百次....一千次...,可想而知,是如许宏大的数据量。
因而虚构DOM
就是为了解决这个浏览器性能问题而被设计进去的。例如后面的例子,如果一次操作中有 10 次更新 DOM 的动作,虚构DOM不会立刻操作DOM
,而是将这 10 次更新 DOM 的动作通过Diff算法
最终生成一个js对象
,而后告诉浏览器去执行一次绘制工作,这样能够防止大量的无谓的计算量
。
什么是虚构 DOM
虚构 DOM[2]就是咱们下面所说的js对象
。
其本质上就是在JS
和DOM
之间做了一个缓存。能够类比 CPU
和硬盘
,既然硬盘
这么慢,咱们就在它们之间加个缓存
:既然 DOM 这么慢,咱们就在它们 JS 和 DOM 之间加个缓存
。CPU(JS)
只操作内存(Virtual DOM)
,最初的时候再把变更写入硬盘(DOM),间接操作内存中的 JS 对象的速度显然要更快。
function vnode(tag, data, key, children, text) { return { tag, data, key, children, text }}
举个栗子:
如果咱们有这样的一个 DOM 树
<ul class="list"> <li class="item">前端简报</li> <li>vue</li></ul>
那么,咱们怎么用 js 的对象来对应到这个树呢?
{ tag: 'ul', // 元素标签 data: { // 属性 class: 'list' }, key: '', text: '', // 文本内容 children: [ { tag: "li", data: { class: "item" }, key: '', text: '', children: [ { tag: undefined, data: undefined, key: undefined, text: '前端简报', children: [] } ] }, { tag: "li", data: "", key: '', text: '', children: [ { tag: undefined, data: undefined, key: undefined, text: 'vue', children: [] } ] } ] // 子元素}
由此可知:DOM tree
的信息都能够用JavaScript对象
来示意,反过来,咱们也能够用 JavaScript对象
示意的树结构来构建一棵真正的DOM树
。
实现虚构 DOM 渲染实在 DOM
有了JavaScript对象
之后如何转化为实在的 DOM 树结构呢?
ul 和 li 在 js 对象中,页面上并没有此构造,所以咱们须要把ul
和li
转化为<ul>
和<li>
标签
而文本标签咱们定义 Vnode 为:
{ tag: undefined, data: undefined, key: undefined, text: 'vue', children: [] }
故能够判断tag
的类型来确定创立元素
的类型.
function createElm(vnode) { let { tag, data, children, key, text } = vnode; if (typeof tag == "string") { vnode.el = document.createElement(tag); //创立元素放到vnode.el上 children.forEach(child => { vnode.el.appendChild(createElm(child)) }) } else { vnode.el = document.createTextNode(text); //创立文本 } return vnode.el}
如果子节点存在并且也是虚构DOM
的话,咱们通过递归
调用创立子节点。
创立 DOM 树结构之后咱们须要设置节点的属性,即解决虚构 DOM 中的data
属性。
function updateProperties(vnode) { let el = vnode.el; let newProps = vnode.data || {}; for (let key in newProps) { if (key == "style") { for (let styleName in newProps.style) { el.style[styleName] = newProps.style[styleName]; } } elseif (key == "class") { el.className = newProps.class; } else { el.setAttribute(key, newProps[key]); } }}
在咱们创立元素标签
之后调用updateProperties
办法即可
把下面创立进去的实在 DOM 构造 vnode.el 增加到文档当中即可呈现出咱们须要的实在 DOM 构造
let parentElm = document.getElementById("app").parentNode; 获取之前app的父亲bodyparentElm.insertBefore(createElm(vnode), document.getElementById("app").nextSibling); //body里在老的app前面插入实在domparentElm.removeChild(document.getElementById("app")); //删除老的节点
总结
以上就是本文的全部内容,我想咱们当初应该理解什么是虚构DOM的概念
了以及虚构DOM是如何实现实在DOM渲染的
。其中用到了次要用到了子节点的递归
,下篇文章将解说_虚构节点的 diff 算法_,敬请期待。
参考资料
虚构DOM介绍: https://www.jianshu.com/p/616...
如何实现一个 Virtual DOM 算法: 'https://github.com/livoras/bl...'