(一)虚拟DOM
虚拟DOM又称为Virtual DOM,简称vDom,vDOM是实现 Vue 和 React 的基石
diff 算法是 vDom 中最核心、最重要的部分
DOM操作是非常耗费性能的
Vue 和 React 是数据驱动视图,是如何简化DOM操作的?
(二)解决方案
将DOM操作计算转化为JS计算,使用js来模拟DOM 架构
js计算速度比DOM计算速度快太多了
给出一个 html 片段 要会转化为 js 对象
(三)时间复杂度
js 对比 两段 DOM转化后的代码 与 对比树类似,先看看树的对比
降低复杂度的方法:3点
tag 表示 标签 a、div、span等
所以vue循环的时候 传一个 :key 有助于vue dom 的比较 性能优化方面渲染更快捷
(四)diff 算法
diff 对比是一个宽泛的概念,两个对象也可以做diff 对比,两棵树也可以做 diff 如vDom diff,其他语言也有diff 算法相关应用
(五)深入diff 算法源码
(1)snabbdom
要了解snabbdom的话有必要先去github上先了解下snabbdom: https://github.com/snabbdom/snabbdom
diff 算法的一个框架体现
以上是主要的几段代码 主要是 h 函数 和 patch 函数 还有 patchVNode、updateChildren函数,这四个函数要搞懂 都是干什么的
虚拟DOM的目的是实现最小更新。
(五)函数解析
npm i snabbdom
(1)首先看 h 函数
主要作用:根据传进来的参数,返回 vNode 对象,vNode 也就是虚拟节点
看下 h 函数的传参
export function h (sel: string, data: VNodeData | null, children: VNodeChildren): VNode
第一个参数:传入的dom元素
第二个参数:dom 元素的特性,比如绑定的事件,style 样式等等
第三个参数:他的子元素,子元素大于1个的话,使用数组包起来
<template>
<div id="app">
<div class="header">
<span>我是文字部分</span>
<div class="intro">这里是介绍</div>
</div>
</div>
</template>
<script>
import { h } from 'snabbdom'
export default {
name: 'App',
mounted () {
let vNode = h('div.header', {}, [h('span', {}, '我是文字部分'), h('div.intro', {}, '这里是介绍')])
console.log(JSON.stringify(vNode, null, 2))
}
}
</script>
<style>
.header {
color: red
}
.intro {
font-size: 13px;
}
</style>
vNode结果:
{
"sel": "div.header",
"data": {},
"children": [
{
"sel": "span",
"data": {},
"text": "我是文字部分"
},
{
"sel": "div.intro",
"data": {},
"text": "这里是介绍"
}
]
}
可以看出 h 函数将 dom 结构转化成 js 对象,来形容dom
说个题外话:前两天看见了组内大哥的骚操作,JSON.stringify 可以传三个参数的,第三个参数是缩进空格,看的方便
(2)patch 函数
patch 函数是 snabbdom 的核心,它的作用是对比两个 vNode(也就是 js 对象),把vNode之间的差异渲染到真实DOM,并返回新的vNode
先来看一下 patch 函数是什么
<template>
</template>
<script>
import { init } from 'snabbdom'
export default {
name: 'App',
mounted () {
// patch 方法是由 init 函数返回的 一个函数中返回另一个函数 也就是高阶函数
let patch = init([])
console.log(patch)
}
}
</script>
<style>
</style>
打印出来:
传入新旧两个vNode,返回值是一个 vNode,在 patch 中又调用了一个新的函数 patchNode,这个函数稍后再讲~
现在让我们看一下 patch 函数的效果
代码:
<template>
<div id="app">
<div class="old">
<span>我是old</span>
</div>
<div class="header">
<span>我是文字部分</span>
<div class="intro">这里是介绍</div>
</div>
</div>
</template>
<script>
import { h, init } from 'snabbdom'
export default {
name: 'App',
mounted () {
let vNode = h('div.header', {}, [h('span', {}, '我是文字部分'), h('div.intro', {}, '这里是介绍')])
console.log(JSON.stringify(vNode, null, 2))
console.log('----------------')
// patch 方法是由 init 函数返回的 一个函数中返回另一个函数 也就是高阶函数
let patch = init([])
console.log(patch)
console.log('------------')
setTimeout(() => {
var appNode = document.querySelector('.old')
var newNode = patch(appNode, vNode)
console.log(JSON.stringify(newNode, null, 2))
}, 3000)
}
}
</script>
<style>
.header {
color: red
}
.intro {
font-size: 13px;
}
</style>
初始:
3s后:
可以看出 patch 函数将页面上 旧 node 更新成了 新的 Node, 并返回 新的 vNode(新的js对象)
(3)patchVNode
作用:patchVnode(oldVnode, newVnode) 函数的作用是对比新旧两个节点,更新它们的差异。
如果sameNode 判断是true 的话,就会走到patchNode 函数中
sameNode函数:判断 oldNode 中的key 和 sel 是否 和 newNode 中的 key 和 sel 是否相同,如果相同返回true;说明他们是相同的Vnode. 否则的话,反之。
patchNode 主要是 进行数据更新、位置移动
(4)updateChildren 函数
在 patchVNode 函数中引出了 updateChildren 函数,updateChildren 函数也是重要的一个函数
updateChildren() 是整个 Virtual DOM 的核心,内部使用 diff 算法,对比新旧节点的 children,更新DOM
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
var oldStartIdx = 0, newStartIdx = 0;
var oldEndIdx = oldCh.length - 1;
var oldStartVnode = oldCh[0];
var oldEndVnode = oldCh[oldEndIdx];
var newEndIdx = newCh.length - 1;
var newStartVnode = newCh[0];
var newEndVnode = newCh[newEndIdx];
var oldKeyToIdx;
var idxInOld;
var elmToMove;
var before;
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVnode == null) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
}
else if (oldEndVnode == null) {
oldEndVnode = oldCh[--oldEndIdx];
}
else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx];
}
else if (newEndVnode == null) {
newEndVnode = newCh[--newEndIdx];
}
else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
}
else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
}
else if (sameVnode(oldStartVnode, newEndVnode)) {
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
}
else if (sameVnode(oldEndVnode, newStartVnode)) {
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
}
else {
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
}
idxInOld = oldKeyToIdx[newStartVnode.key];
if (isUndef(idxInOld)) {
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
newStartVnode = newCh[++newStartIdx];
}
else {
elmToMove = oldCh[idxInOld];
if (elmToMove.sel !== newStartVnode.sel) {
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
}
else {
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
oldCh[idxInOld] = undefined;
api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);
}
newStartVnode = newCh[++newStartIdx];
}
}
}
if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
if (oldStartIdx > oldEndIdx) {
before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
}
else {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
}
}
}
循环遍历 具体对比过程看下面文章吧~
更多学习查看文章:https://blog.csdn.net/u012961419/article/details/107596868