这是我参与更文挑战的第6天,活动详情查看: 更文挑战javascript
标题灵感来源评论区:html
什么是虚拟 DOM?简单来讲,虚拟 DOM 就是一个模拟真实 DOM 的树形结构,这个树结构包含了整个 DOM 结构的信息。java
正常咱们看到的真实 DOM 是这样的:node
而虚拟 DOM 则是这样的,包含了标签名称、标签属性、子节点等真实 DOM 信息:react
虚拟 DOM 既然是模拟真实 DOM 的树形结构,那么为何要用虚拟 DOM 呢?直接操做 DOM 有什么缺点吗?git
直接操做 DOM 没有缺点,可是频繁的操做 DOM 就缺点很大,由于操做 DOM 会引发重排,频繁操做 DOM 时,浏览器会频繁重排,致使页面卡顿。github
浏览器渲染的大体流程以下:web
重排(也叫回流、reflow)就是当涉及到 DOM 节点的布局属性发生变化时,就会从新计算该属性,浏览器会从新描绘相应的元素(上述第 4 步)。算法
DOM Tree 里的每一个节点都会有 reflow 方法,一个节点的 reflow 颇有可能致使子节点,甚至父点以及同级节点的 reflow。编程
所以,为了提高性能,咱们应该尽可能减小 DOM 操做。
当有一个表格须要作排序功能时,有出生年月、性别等排序方式可选,当选择某排序方式时,表格将按该方式从新排序。
从上可知,虚拟 DOM 经过 diff 算法,帮助咱们大量的减小 DOM 操做。
从另外一个角度看,虚拟 DOM 为咱们提供了函数式的编程方式,使代码可读性和可维护性更高。
注:该章节的虚拟 DOM 实现原理并非参比 React 源码,而是参比 simple-virtual-dom,可经过该章节简单了解虚拟 DOM 实现原理,React 中的虚拟 DOM 实现可查看 React 官网 Virtual DOM 及内核。
虚拟 DOM 经过如下步骤实现:
模拟真实 DOM 树,构建虚拟 DOM 树结构,包含标签名 tagName、属性对象 props、子节点 children、子节点数 count 等属性。
function Element (tagName, props = {}, children = []) {
// 标签名
this.tagName = tagName
// 属性对象
this.props = props
// 子节点
this.children = children
// key标志
const { key = void 666 } = this.props
this.key = key
// 子节点数量
let count = 0
this.children.forEach((child, index) => {
if (child instanceof Element) {
count += child.count
}
count++
})
this.count = count
}
复制代码
建立虚拟 DOM 对象:
console.log(el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom'])
]))
复制代码
生成的虚拟 DOM 对象如图:
将虚拟 DOM 转换为真实 DOM:
Element.prototype.render = function () {
const el = document.createElement(this.tagName)
const props = this.props
for (const propName in props) {
const propValue = props[propName]
_.setAttr(el, propName, propValue)
}
this.children.forEach((child) => {
let childEl
if (child instanceof Element) {
childEl = child.render()
} else {
childEl = document.createTextNode(child)
}
el.appendChild(childEl)
})
return el
}
复制代码
填充进页面:
document.body.appendChild(el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom'])
]).render())
复制代码
效果如图:
当数据更新时,须要对新旧虚拟 DOM 树进行对比。
if (_.isString(oldNode) && _.isString(newNode)) {
if (newNode !== oldNode) {
currentPatch.push({ type: patch.TEXT, content: newNode })
}
// Nodes are the same, diff old node's props and children
}
复制代码
if (
oldNode.tagName === newNode.tagName &&
oldNode.key === newNode.key
) {
// Diff props
var propsPatches = diffProps(oldNode, newNode)
if (propsPatches) {
currentPatch.push({ type: patch.PROPS, props: propsPatches })
}
// Diff children. If the node has a `ignore` property, do not diff children
if (!isIgnoreChildren(newNode)) {
diffChildren(
oldNode.children,
newNode.children,
index,
patches,
currentPatch
)
}
}
复制代码
currentPatch.push({
type: PATCH_KEY.REPLACE,
node: newNode
})
复制代码
总结一下,虚拟 DOM 只在同层级间 Diff,若是标签不一样则直接替换该节点及其子节点。
尝试对比虚拟 DOM 以下:
function renderTree () {
return el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom']),
el('p', ['the count is :' + Math.random()])
])
}
let tree = renderTree()
setTimeout(() => {
const newTree = renderTree()
const patches = diff(tree, newTree)
console.log(patches)
}, 2000)
复制代码
对比差别为 p 标签的文本节点发生改变,输出结果如图:
最后一步是根据 diff 结果,对真实 DOM 进行修改。
遍历真实 DOM 树,若是该 DOM 节点有 diff,则根据 diff 类型,处理 DOM 节点,若是该 DOM 节点无 diff,则遍历其子节点,直至遍历完成。
注:React 实现更优,具体请见 React fiber。
function patch (node, patches) {
var walker = {index: 0}
dfsWalk(node, walker, patches)
}
function dfsWalk (node, walker, patches) {
var currentPatches = patches[walker.index]
var len = node.childNodes
? node.childNodes.length
: 0
for (var i = 0; i < len; i++) {
var child = node.childNodes[i]
walker.index++
dfsWalk(child, walker, patches)
}
if (currentPatches) {
applyPatches(node, currentPatches)
}
}
复制代码
尝试更新真实 DOM,代码以下:
function renderTree () {
return el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom']),
el('p', ['the count is :' + Math.random()])
])
}
let tree = renderTree()
const root = tree.render()
document.body.appendChild(root)
setTimeout(() => {
const newTree = renderTree()
const patches = diff(tree, newTree)
patch(root, patches)
tree = newTree
}, 2000)
复制代码
效果如图:
上图可见,成功更新真实 DOM。
本文从什么是虚拟 DOM、为何使用虚拟 DOM、虚拟 DOM 的实现原理等 3 个角度对虚拟 DOM 进行讲述。
虚拟 DOM 经过模拟真实 DOM 的树结构,收集大量 DOM 操做,经过 diff 算法对真实 DOM 进行最小化修改,减小浏览器重排,提高加载速度,达到优化网站性能的做用。
虚拟 DOM 采用函数式编程,让咱们码得更好看更快乐。
可经过 github源码 进行实操练习。
但愿能对你有所帮助,感谢阅读~
别忘了点个赞鼓励一下我哦,笔芯❤️