Virtual DOM
Virtual DOM
通过 JS 对象模拟 DOM 对象
绑定了视图和状态
按需更新DOM
JS表示DOM
export default class VNode {
constructor (
tag, // 标签名
children, // 子节点
text, // 文本节点
elm // 对应的真实dom对象
) {
this.tag = tag
this.children = children
this.text = text
this.elm = elm
}
}
import VNode from 'vnode'
// 创建非文本节点
var c = (tag, children) => {
return VNode(tag, children,'')
}
// 创建文本节点
var t = (text) => {
return VNode(null, [], text)
}
JS表示DOM
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
let ul =
c('ul', [
c('li', [ t("Item 1") ]),
c('li', [ t("Item 2") ]),
c('li', [ t("Item 3") ])
])
VD渲染为DOM
1. 创建根节点
2. 创建子节点
3. 挂载到父节点
VD渲染为DOM
function createElm (vnode, parentElm, refElm) {
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
vnode.elm = nodeOps.createElement(tag)
createChildren(vnode, children)
insert(parentElm, vnode.elm, refElm)
} else { // 文本节点
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
function createChildren (vnode, children) {
for (let i = 0; i < children.length; ++i) {
createElm(children[i], vnode.elm, null)
}
}
function insert (parent, elm, ref) {
if (parent) {
if (ref) {
document.insertBefore(parent, elm, ref)
} else {
document.appendChild(parent, elm)
}
}
}
createElm(
ul, // VNode
document.getElementById("container"),
null // refElm
)
DIFF
patch (oldVnode, newVnode)
两个树的完全的 diff 算法的时间复杂度为 O(n^3)
前端中很少跨层移动DOM
Virtual DOM 只会对同一个层级的元素进行对比
prerequisite
DIFF
1. oldVnode和newVode节点不一致
function patch (oldVnode, newVnode) {
if (sameVnode(oldVnode, newVnode)) {
} else {
// 1. 找到 oldVnode 对应的 DOM 节点及其父节点
const oldElm = oldVnode.elm
const parentElm = document.parentNode(oldElm)
// 2. 把当前的 vnode 渲染在原来的父亲节点下
createElm(
newVnode,
parentElm,
document.nextSibling(oldElm)
)
// 3. 把旧的 DOM 节点从原来的 DOM 树上移除
removeNode(oldVnode.elm)
}
}
DIFF
2. oldVnode和newVode节点不一致
function patch (oldVnode, newVnode) {
if (sameVnode(oldVnode, newVnode)) {
// 如果两个 vnode 节点根一致
updateChildren(oldVnode.children, newVnode.children)
} else {
}
DIFF
列表对比算法
最小编辑距离问题
通过动态规划求解,时间复杂度为 O(M * N)
这里偷个懒,用一个简单的算法来做
DIFF
updateChildren
定义四个指针:
oldStartVnode
oldEndVnode
newStartVnode
newEndVnode
Step 1
DIFF
updateChildren
Step 2
遍历对比
oldStartVnode 和 newStartVnode 相同
function updateChildren (parentElm, oldCh, newCh) {
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if () {
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
}
}
}
DIFF
updateChildren
Step 2
遍历对比
oldEndVnode 和 newEndVnode 相同
function updateChildren (parentElm, oldCh, newCh) {
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if () {
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
}
}
}
DIFF
updateChildren
Step 2
遍历对比
oldStartVnode 和 newEndVnode 相同
(oldStartDom 挪到 oldEndDom后面)
function updateChildren (parentElm, oldCh, newCh) {
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if () {
} else if (sameVnode(oldStartVnode, newEndVnode)) {
patchVnode(oldStartVnode, newEndVnode)
parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
}
}
}
DIFF
updateChildren
Step 2
遍历对比
oldEndVnode 和 newStartVnode相同
(oldEndDom 挪到 oldStartDom 之前)
function updateChildren (parentElm, oldCh, newCh) {
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if () {
} else if (sameVnode(oldEndVnode, newStartVnode)) {
patchVnode(oldEndVnode, newStartVnode)
parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
}
}
}
DIFF
updateChildren
Step 2
遍历对比
其他情况
直接插入
function updateChildren (parentElm, oldCh, newCh) {
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if () {
} else {
createElm(newStartVnode, parentElm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
}
}
}
DIFF
updateChildren
Step 3
处理剩余节点
function updateChildren (parentElm, oldCh, newCh) {
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx)
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
DIFF
辅助函数patchVnode
function patchVnode (oldVnode, vnode, removeOnly) {
if (oldVnode === vnode) {
return
}
const elm = vnode.elm = oldVnode.elm
const oldCh = oldVnode.children
const ch = vnode.children
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, removeOnly)
} else if (isDef(ch)) {
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1)
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text)
}
}
DEMO
Virtual DOM
By Joson Chen
Virtual DOM
- 516