虚拟 DOM 是什么
在操作真实 DOM 的过程中,JavaScript 的执行时间是很快的,但是浏览器在渲染页面的时候,会让页面不可交互。虚拟 DOM 是一个和真实 DOM 对应的概念。它是对 DOM 的抽象,本质上是 JavaScript 对象,这个对象就是更加轻量级的对 DOM 的描述,优化 DOM 的操作。
真实 DOM:
<div class="red">
<span></span>
<span></span>
</div>
虚拟 DOM (React):
const vNode = {
key: null,
type: "div", // 标签名或组件名
props: {
children: [ // 子元素节点
{type: 'span', ...},
{type: 'span', ...}
],
className: "red", // 标签上的属性
onClick: () => {} // 事件
},
ref: null,
...
}
虚拟 DOM (Vue):
const vNode = {
tag: "div", // 标签名或组件名
data: {
class: "red", // 标签上的属性
on: {
click: () => {} // 事件
}
},
children: [ // 子元素节点
{tag: "span", ...},
{tag: "span", ...}
],
...
}
如何创建虚拟 DOM
- React.createElement
createElement('div', {className:'red', onClick: () => {}}, [
createElement('span', {}, 'span1'),
createElement('span', {}, 'span2')
]
)
- Vue(只能在 render 函数里得到 h)
h('div', {
class: 'red',
on: {
click: () => {}
},
}, [h('span', {}, 'span1'), h('span', {}, 'span2')])
简化创建虚拟 DOM
- React JSX:
<div className="red" onClick={fn}>
<span>span1</span>
<span>span2</span>
</div>
这种写法很像真实 DOM,但是不是真实 DOM。因为真实 DOM 没有 className
、onClick
这种写法,标签里不写 {}
。
通过 babel
转为 createElement
形式
- Vue Template:
<div class="red" @click="fn">
<span>span1</span>
<span>span2</span>
</div>
通过 vue-loader
转为 h
形式
虚拟 DOM 的优点
- 减少 DOM 操作
虚拟 DOM 可以将多次操作合并为一次操作,比如我们添加 1000 个节点,真实 DOM 操作却是一个接一个操作的。
虚拟 DOM 借助 DOM diff 可以把多余的操作省掉,比如我们添加 1000 个节点,其实只有 10 个是新增的。
- 跨平台
虚拟 DOM 不仅可以变成 DOM,还可以变成小程序、iOS 应用、安卓应用,因为虚拟 DOM 本质上是一个 JavaScript 对象。这也就是虚拟 DOM 最初的目的。比如 Node.js 就没有 DOM,如果想实现 SSR (服务端渲染),那么一个方式就是借助虚拟 DOM。
虚拟 DOM 的缺点
需要额外的创建函数,例如 createElement
或 h
,但可以通过 JSX 来简化成 XML 写法,但是这种写法严重依赖打包工具,要添加额外的构建过程。
DOM diff 是什么
DOM diff 是虚拟 DOM 的对比算法,是用来对比差异的算法。
我们将虚拟 DOM 想象成树形
<div :class="x">
<span v-if="y">{span1}</span>
<span>{span2}</span>
</div>
这段代码我们会得到一颗虚拟 DOM 树:
当数据变化时:
- x 从 red 变成 green
- DOM diff 发现
div 标签类型没变,只需要更新 div 对应的 DOM 的属性
子元素没变,不更新
- y 从 true 变成 false
- DOM diff 发现
div 没变,不用更新
子元素1标签没变,但是 children 变了,更新了 DOM 内容
子元素2不见,删除对应的 DOM
总结:DOM diff 就是一个函数,我们称之为 patch
。其用法为 patches = patch(oldVNode, newVNode)
,patches 就是要运行的 DOM 操作。由于 Vue 和 React diff 算法是不一致的,它们的操作是不一致的,但是主体特征是不会变的,都是需要给两个虚拟 DOM 节点,得到对应的 DOM 操作,这个操作不会立即执行,而是等全部的虚拟 DOM 节点更新完了,再一次性把这些 DOM 操作依次执行。
我们已经知道了 DOM diif 的结构,现在我们来了解一下 DOM diff 的大概逻辑。
Tree diff
- 将新旧两棵树逐层对比,找出哪些节点需要更新
- 如果节点是组件,就看 Component diff
- 如果节点是标签,就看 Element diff
Component diff
如果节点是组件,就先看组件类型,类型不同直接替换(删除旧的);类型相同则只更新属性,然后深入组件做 Tree diff(递归)。
Element diff
如果节点是原生标签,则看标签名。标签名不同直接替换,相同则只更新属性,然后进入标签后代做 Tree diff (递归)。
通过以上分析,DOM diff 对树的逐层对比,实现了减少 DOM 操作的次数。
DOM diff 的问题
如上 span 节点的删除图例,DOM diff 要做的更新操作是,检查第一个 span 的内容 hello 变为 world ,然后检查第二个 span 被删除,于是要做一个更新和一个删除的操作。而不是直接删除第一个 span ,将第二个 span 左移的过程。
那么这里就出现了问题,我们来看下一个例子:
通过这个在线例子 点击查看 ,我们可以看出如下问题
点击第二个删除之后:
我们删除了数组的 2
,得到长度为 2 的新数组,2
更新为 3
,但是内容 二
不变。那么为什么会出现这个问题。
计算机认为我们把 2
变成了 3
,然后删除了 3
。
首先,计算机通过遍历进行比对,对比 1 和 1 ,发现 "1 没变" 。然后对比 2 和 3 ,发现 "2 变成了 3" 。最后对比 undefined 和 3 ,发现 "3 被删除了" 。
所以就得到了这个结果。
解决方法也很简单,我们只需要配置一个唯一值作为 key 就可以了,让计算机根据这个 key ,知道我们操作的是哪个 DOM ,这样计算机就可以直接删除 2 了。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!