vue的虚拟dom和diff算法

1.虚拟dom

虚拟dom,我的理解就是通过js对象的方式来具体化每一个节点,把dom树上面的每个节点都变为对象里的一个元素,元素的子元素变为子节点,节点上面的class、id、attribute等属性变为data内的值,然后通过dom上面的createElement、appendChild、insertBefore等方法进行生成dom树。

let VNode = {
sel:'div',
data:{
key:0,
props:{},
attrs:{},
class:{},
style:{},
fn:{}
},
text:'虚拟dom',
elm:'<div>虚拟dom</div>'
children:[
{
sel:'div',
data:{
key:0,
props:{},
attrs:{},
class:{},
style:{},
fn:{}
},
text:'虚拟dom children',
elm:'<div>虚拟dom children</div>'
children:[]
}
]
}

2.diff算法

看了diff算法后感觉写的真是巧妙,真正做到了最小量更新 。

diff是当父节点相同时用来对子节点进行最小量更新的算法。

diff算法采用四个指针:旧节点开始指针,旧节点结束指针,新节点开始指针,新节点结束指针;

(上方虚拟节点中的key就是为了在进行diff算法时判断是否是同一个节点便于最小量更新)

while(旧节点开始指针<=旧节点结束指针&&新节点开始指针<=新节点结束指针){

分为以下五种情况:(前四种情况)

  当进行下面5种判断后可能会出现新节点[新节点开始指针]   旧节点[旧节点开始指针]   新节点[新节点结束指针]   旧节点[旧节点结束指针]为空值的情况,如果出现空值则代表当前节点已经处理过了,所以就需要将指针++或者--

if(旧节点[旧节点开始指针] ==null){

旧节点开始指针++

}else if(旧节点[旧节点结束指针]==null){

旧节点结束指针--

}else if(新节点[新节点开始指针] ==null){

新节点开始指针++

}else if(新节点[新节点结束指针] ==null){

新节点结束指针--

}

1、新节点[新节点开始指针] 对比  旧节点[旧节点开始指针]

  如果符合此种情况,则代表新节点[新节点开始指针] 旧节点[旧节点开始指针] 为同一个节点,实行最小量更新,即只更新节点内的属性而不进行dom销毁创建操作,完成更新后 新节点开始指针++旧节点开始指针++

2、新节点[新节点结束指针] 对比 旧节点[旧节点结束指针]

  如果符合此种情况,则代表新节点[新节点结束指针] 旧节点[旧节点结束指针] 为同一个节点,实行最小量更新,即只更新节点内的属性而不进行dom销毁创建操作,完成更新后 新节点结束指针--旧节点结束指针--

3、新节点[新节点结束指针] 对比 旧节点[旧节点开始指针]

  如果符合此种情况,则代表新节点[新节点结束指针] 旧节点[旧节点开始指针] 为同一个节点,实行最小量更新,先更新节点内的属性,然后使用insertBefore将旧节点[旧节点开始指针] 移动到旧节点[旧节点结束指针] 之后,(注意:此处要移动到旧节点[旧节点结束节点] 后,而不是所有旧节点后,因为这里的旧节点结束指针是会变化的),

    父节点.insertBefore(旧节点[旧节点开始指针].elm, 旧节点[旧节点结束指针].elm.nextSibling)

  完成操作后 新节点结束指针--旧节点开始指针++

4、新节点[新节点开始指针] 对比 旧节点[旧节点结束指针] 如果符合此种情况,则代表新节点[新节点开始指针]旧节点[旧节点结束指针] 为同一个节点,实行最小量更新,先更新节点内的属性,然后使用insertBefore将旧节点[旧节点结束指针] 移动到旧节点[旧节点开始指针] 前,(注意:此处要移动到旧节点[旧节点开始指针] 前,而不是所有旧节点前,因为旧节点开始指针也是会发生变化的)

    父节点.insertBefore(旧节点[旧节点结束指针].elm, 旧节点[旧节点开始指针].elm)

  完成操作后,旧节点结束指针--新节点开始指针++

5、遍历旧节点数组,生成一个以key为键,index为值的对象为旧节点keyIndexMap,然后查询新节点[新节点开始指针]中的key是否在旧节点keyIndexMap中存在;

  如果不存在,则证明新节点[新节点开始指针]在旧节点列表中不存在,此时需要创建新节点[新节点开始指针]为真实dom,并将其插入至旧节点[旧节点开始指针]前(因为此时新节点[新节点开始指针]一定处于全部未处理的旧节点前)

父节点.insertBefore(创建dom(新节点[新节点开始指针]), 旧节点[旧节点开始指针].elm)

  如果存在则先需要判断旧节点[旧节点keyIndexMap[新节点[新节点开始指针][key]]]新节点[新节点开始指针]的sel(标签)是否相同:

    如果相同则代表为同一个标签,则进行最小量更新,先更新节点内的属性,然后insertBefore将旧节点[旧节点keyIndexMap[新节点[新节点开始指针][key]]]移动到旧节点[旧节点开始指针] 前,然后将旧节点[旧节点keyIndexMap[新节点[新节点开始指针][key]]]设置为undefined,代表当前节点处理过了;

    如果不同则代表不是同一个标签,则只创建新节点[新节点开始指针]的真实dom,然后将其插入到旧节点[旧节点开始节点]

  最后新节点开始指针++

}

当以上循环完成后可能还会出现没有处理到的节点,所以还需要再查找没有处理到的节点:

  如果是新节点开始指针<=新节点结束指针,则代表新节点列表内还有没有处理的节点,没有处理的节点全部为新增节点,此时需要遍历新节点[新节点开始指针](包含)至新节点[新节点结束指针](包含)之间的节点,然后将其添加至新节点[新节点结束指针+1]之前(新节点[新节点结束指针+1]可能为空,新节点[新节点结束指针+1]为空时可添加到最后)

        for (let i = 新节点开始节点; i <= 新节点结束节点; i++) {
//insertBefore可以自动识别空值,如果是空值,则插入到最后
父节点.insertBefore(创建dom(新节点[i]), 新节点[新节点结束节点-1]?.elm)
}

   如果是旧节点开始指针<=旧节点结束指针,则代表旧节点内还有没有处理的节点,没有处理的节点全部为需要删除节点,此时需要遍历旧节点[旧节点开始指针](包含)至旧节点[旧节点结束指针](包含) 之间的节点,然后将其全部删除。

        for (let i = 旧节点开始指针; i <= 旧节点结束指针; i++) {
旧节点[i] && (父节点.removeChild(旧节点[i].elm))
}

以上就是我对diff算法的理解,下面贴上代码(阉割版,部分情况没有考虑,旨在学习diff算法,可能会有bug):

//updateChildren文件
import { sameVnode } from './is'
import patchVnode from './patchVnode'
import createElement from './createElement' export default functionupdateChildren (parentElm, oldCh, newCh) {
console.log('updateChildren')
console.log(parentElm, oldCh, newCh) //旧前
let oldStartIdx = 0
//新前
let newStartIdx = 0
//旧后
let oldEndIdx = oldCh.length - 1
//新后
let newEndIdx = newCh.length - 1
//旧节点
let oldStartVnode = oldCh[0]
//旧后节点
let oldEndVnode = oldCh[oldEndIdx]
//新节点
let newStartVnode = newCh[0]
//新后节点
let newEndVnode = newCh[newEndIdx] let keyMap = {}
// 开始循环
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
// debugger
console.log('while')
if (oldStartVnode === undefined || oldCh[oldStartIdx] === undefined) {
oldStartVnode = oldCh[++oldStartIdx]
} else if (oldEndVnode === undefined || oldCh[oldEndIdx] === undefined) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (newStartVnode === undefined || newCh[newStartIdx] === undefined) {
newStartVnode = newCh[++newStartIdx]
} else if (newEndVnode === undefined || newCh[newEndIdx] === undefined) {
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
//新前和旧前是同一个节点
console.log('新前和旧前是同一个节点')
patchVnode(oldStartVnode, newStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {//旧后和新后是同一个节点
console.log('旧后和新后是同一个节点')
patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) {//新后和旧前是同一个节点
console.log('新后和旧前是同一个节点')
patchVnode(oldStartVnode, newEndVnode)
//当新后节点是旧前节点时,此时需要移动节点,移动旧前节点到旧后的后面
parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) {//旧后和新前是同一个节点
console.log('旧后和新前是同一个节点')
// 当旧后和新前是同一个节点时,此时需要移动旧后节点到旧前节点的前面
patchVnode(oldEndVnode, newStartVnode)
parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
//前四种都没有命中
if (Object.keys(keyMap).length === 0) {
keyMap = {}
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
const key = oldCh[i].key
if (key) {
keyMap[key] = i
}
}
}
console.log(keyMap)
//寻找当前节点在keyMap中的位置
const idxInOld = keyMap[newStartVnode.key]
console.log(idxInOld)
if (!idxInOld) {
//新节点不在旧节点中
parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
} else {
// 新节点在旧节点中,需要移动
const elmToMove = oldCh[idxInOld]
if (elmToMove.sel !== newStartVnode.sel) {
parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
} else {
patchVnode(elmToMove, newStartVnode)
// 把这项设置为undefined,表示已经移动过了
oldCh[idxInOld] = undefined
parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm)
}
}
//指针向后移动
newStartVnode = newCh[++newStartIdx]
}
}
// 继续查询是否有剩余节点
if (newStartIdx <= newEndIdx) {
console.log('新节点还有剩余节点没有处理', newStartIdx, newEndIdx)
const before = newCh[newEndIdx + 1]?.elm
console.log(before)
for (let i = newStartIdx; i <= newEndIdx; i++) {
//insertBefore可以自动识别undefined,如果是undefined,则插入到最后
parentElm.insertBefore(createElement(newCh[i]), before)
}
} else if (oldStartIdx <= oldEndIdx) {
console.log('旧节点还有剩余节点没有处理', oldStartIdx, oldEndIdx)
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
oldCh[i] && (parentElm.removeChild(oldCh[i].elm))
}
}
} let arr = [1, 1, 2, 35, 9, 2, 9]
arr.reduce((p, n) => {
return p ^ n
}, 0)
//is文件
export function sameVnode (vnode1, vnode2) {
return vnode1.sel === vnode2.sel && vnode1.key === vnode2.key;
}
//createElement文件
//真正创建dom
export default function createElement (vnode) {
let domNode = document.createElement(vnode.sel);
if (
vnode.text !== "" &&
(vnode.children === undefined || vnode.children.length === 0)
) {
domNode.innerText = vnode.text;
// 补充elm } else if (Array.isArray(vnode.children) && vnode.children.length > 0) {
for (let i = 0; i < vnode.children.length; i++) {
domNode.appendChild(createElement(vnode.children[i]));
}
}
vnode.elm = domNode;
return vnode.elm
}
//patchVnode文件
import createElement from './createElement'
import updateChildren from './updateChildren' export default function patchVnode (oldVnode, newVnode) {
// console.log('patchVnode')
if (oldVnode === newVnode) return
if (newVnode.text && (!newVnode.children || newVnode.children.length === 0)) {//判断newVnode的text是否为空,且不等于oldVnode的text,如果满足以上条件,则更新text
oldVnode.text !== newVnode.text && (oldVnode.elm.innerText = newVnode.text);
} else {//newVnode的text为空,则判断newVnode的children是否为空,如果不为空,则更新children
// 新节点没有text属性
if (oldVnode.children && oldVnode.children.length > 0) {
// 老节点有children,新节点也有children
updateChildren(oldVnode.elm, oldVnode.children, newVnode.children);
} else {
// 老的没有children,新的有children
oldVnode.elm.innerHTML = '';
for (let i = 0; i < newVnode.children.length; i++) {
let dom = createElement(newVnode.children[i])
oldVnode.elm.appendChild(dom)
}
}
}
}
//patch文件
import vnode from "./vnode";
import createElement from "./createElement";
import patchVnode from './patchVnode'
import { sameVnode } from './is' export default function (oldVnode, newVnode) {
// console.log(oldVnode, newVnode)
//判断传入的第一个参数,是dom节点还是vnode
if (oldVnode.sel === "" || oldVnode.sel === undefined) {
//传入的如果是dom节点需要包装为虚拟节点
oldVnode = vnode(
oldVnode.tagName.toLowerCase(),
{},
[],
undefined,
oldVnode,
);
}
// 判断oldVnode和newVnode是否是同一个节点
if (sameVnode(oldVnode, newVnode)) {
// console.log("是同一个节点");
patchVnode(oldVnode, newVnode);
} else {
// console.log("不是同一个节点");
let newVnodeElm = createElement(newVnode, oldVnode.elm);
if (oldVnode.elm.parentNode && newVnodeElm) {
oldVnode.elm.parentNode.insertBefore(newVnodeElm, oldVnode.elm);
}
oldVnode.elm.parentNode.removeChild(oldVnode.elm);
}
}
//VNode文件
export default function (sel, data, children, text, elm) {
const key = data.key
return {
sel,
data,
children,
text,
elm,
key,
}
}
//h文件

import vnode from './vnode.js'

//h('div',{},'文字')
//h('div',{},'[]')
//h('div',{},h())
export default function (sel, data, c) {
//检查参数个数
if (arguments.length !== 3) {
throw new Error('h()参数个数不正确') }
// 检查C类型
if (typeof c === 'string' || typeof c === 'number') {
return vnode(sel, data, undefined, c, undefined)
} else if (Array.isArray(c)) {
let children = []
for (let i = 0; i < c.length; i++) {
if (!(typeof c[i] === 'object' && c[i].hasOwnProperty('sel'))) {
throw new Error('传入的数组参数中有项不是h函数')
}
children.push(c[i])
}
// 循环结束,children收集完毕
return vnode(sel, data, children, undefined, undefined)
} else if (typeof c === 'object' && c.hasOwnProperty('sel')) {
let children = [c]
return vnode(sel, data, children, undefined, undefined)
} else {
throw new Error('h()参数类型不正确')
}
}

vue虚拟dom和diff算法的更多相关文章

  1. 【React 7/100 】 虚拟DOM和Diff算法

    虚拟DOM和Diff算法 React更新视图的思想是:只要state变化就重新渲染视图 特点:思路非常清晰 问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染吗? 不是这样的 理想 ...

  2. 虚拟DOM与diff算法

    虚拟DOM与diff算法 虚拟DOM 在DOM操作中哪怕我们的数据,发生了一丢丢的变化,也会被强制重建整预DOM树.这么做,涉及到很多元素的重绘和重排,导致性能浪费严重 只要实现按需更新页面上的元素即 ...

  3. 深入理解react中的虚拟DOM、diff算法

    文章结构: React中的虚拟DOM是什么? 虚拟DOM的简单实现(diff算法) 虚拟DOM的内部工作原理 React中的虚拟DOM与Vue中的虚拟DOM比较 React中的虚拟DOM是什么?   ...

  4. vue之虚拟DOM、diff算法

    一.真实DOM和其解析流程? 浏览器渲染引擎工作流程都差不多,大致分为5步,创建DOM树——创建StyleRules——创建Render树——布局Layout——绘制Painting 第一步,用HTM ...

  5. 虚拟dom?diff算法?key?Vue原理的核心三问?打包教你搞定。

    为什么需要虚拟DOM 先介绍浏览器加载一个HTML文件需要做哪些事,帮助我们理解为什么我们需要虚拟DOM.webkit引擎的处理流程,如下图所示: 所有浏览器的引擎工作流程都差不多,如上图大致分5步: ...

  6. 虚拟dom与diff算法 分析

    好文集合: 深入浅出React(四):虚拟DOM Diff算法解析 全面理解虚拟DOM,实现虚拟DOM

  7. react中虚拟dom的diff算法

    .state 数据 .jsx模板 .生成虚拟dom(虚拟DOM就是一个js对象,用它来描述真实DOM) ['div', {id:'abc'}, ['span', {}, 'hello world']] ...

  8. 【前端知识体系-JS相关】虚拟DOM和Diff算法

    1.介绍一下vdom? virtual dom, 虚拟DOM 使用JS来模拟DOM结构 DOM变化的对比,放在JS层来做(图灵完备语言),提高效率 DOM操作非常昂贵(消耗性能) 2.Snabbdom ...

  9. 【React自制全家桶】二、分析React的虚拟DOM和Diff算法

    一.React如何更新DOM内容: 1.  获取state 数据 2.  获取JSX模版 3.  通过数据 +模版结合,生成真实的DOM, 来显示,以下行代码为例(简称代码1) <div id= ...

随机推荐

  1. Java报错: A component required a bean of type 'com.sirifeng.testmybatis.mapper.BookMapper' that could not be found.

    在学习Spring-boot-mybatis时,报错A component required a bean of type 'com.sirifeng.testmybatis.mapper.BookM ...

  2. OpenHarmony 3.1 Beta 版本关键特性解析——ArkUI canvas组件

    (以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点) 江英杰 华为技术有限公司 canvas 是 ArkUI 开发框架里的画布组件,常用于自定义绘制图形.因为其轻量.灵活. ...

  3. 你能知道的或者不知道的shell变量都在这里

    第2章 shell变量讲解 2.1 shell中的变量讲解 2.1.1 什么是shell变量 变量的本质就是内存中的一块区域 变量名 位置 变量是脚本中经常会使用的内容信息 变量可以在脚本中直接使用 ...

  4. HashSet与TreeSet的区别

    HashSet元素唯一,无序,依靠hashcode(),toString()实现元素的唯一性 TreeSet元素唯一,有序,依靠bTo实现比较,即继承Comparable类并重写compareTo(O ...

  5. python数据处理-matplotlib入门(4)-条形图和直方图

    摘要:先介绍条形图直方图,然后用随机数生成一系列数据,保存到列表中,最后统计出相关随机数据的概率并展示 前述介绍了由点进行划线形成的拆线图和散点形成的曲线图,连点成线,主要用到了matplotlib中 ...

  6. 【导包】使用Sklearn构建Logistic回归分类器

    官方英文文档地址:http://scikit-learn.org/dev/modules/generated/sklearn.linear_model.LogisticRegression.html# ...

  7. 【ACM程序设计】前缀和

    前缀和 ​ 前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和 作用: 一种预处理,求出的前缀和数组可以使得,输出原序列中从第l个数到第r个数和的时间复杂度变成了O(1) . 一维前缀和 ...

  8. junethack使用指南

    本文面向有志于参加Nethack六月衍生大赛,且具有一定英文水平的玩家. 首先,在Junethack服务器页面挑一个在线服务器的网站,个人推荐 hardfought.org,因为访问速度较快. 然后, ...

  9. 华硕主板安装Ubuntu双系统无法启动解决办法

    问题描述: 在安装完Ubuntu后,开机后没有启动项可以选择,而是直接进入win10系统. 解决办法: 开机进入bios,选择 高级模式 (Advance model),进入 启动(boot)选项卡, ...

  10. HashMap中红黑树插入节点的调整过程

    如果有对红黑树的定义及调整过程有过研究,其实很容易理解HashMap中的红黑树插入节点的调整过程. "红黑树定义及调整过程"的参考文章:<红黑树原理.查找效率.插入及变化规则 ...