位置: IT常识 - 正文

vue虚拟dom和diff算法(vue虚拟domdiff算法)

编辑:rootadmin
vue的虚拟dom和diff算法 1.虚拟dom 虚拟dom,我的理解就是通过js对象的方式来具体化每一个节点,把dom树上面的每个节点都变为对象里的一个元素,元素的子元素变为子节点,节点上面的class、id、attribute等属性变为data内的值,然后通过dom上面的createElemen ... vue的虚拟dom和diff算法1.虚拟dom

推荐整理分享vue虚拟dom和diff算法(vue虚拟domdiff算法),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:vue 虚拟dom diff,vue 虚拟dom diff,vue虚拟dom和diff算法简洁回答,vue 虚拟dom diff,vue虚拟dom和diff算法,vue虚拟dom和diff算法,vue虚拟dom和diff算法简洁回答,vue虚拟dom和diff算法,key的作用,内容如对您有帮助,希望把文章链接给更多的朋友!

虚拟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){

新节点结束指针--

vue虚拟dom和diff算法(vue虚拟domdiff算法)

}

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) {// debuggerconsole.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].keyif (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] = undefinedparentElm.insertBefore(elmToMove.elm, oldStartVnode.elm)}}//指针向后移动newStartVnode = newCh[++newStartIdx]}}// 继续查询是否有剩余节点if (newStartIdx <= newEndIdx) {console.log('新节点还有剩余节点没有处理', newStartIdx, newEndIdx)const before = newCh[newEndIdx + 1]?.elmconsole.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文件//真正创建domexport 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) returnif (newVnode.text && (!newVnode.children || newVnode.children.length === 0)) {//判断newVnode的text是否为空,且不等于oldVnode的text,如果满足以上条件,则更新textoldVnode.text !== newVnode.text && (oldVnode.elm.innerText = newVnode.text);} else {//newVnode的text为空,则判断newVnode的children是否为空,如果不为空,则更新children// 新节点没有text属性if (oldVnode.children && oldVnode.children.length > 0) {// 老节点有children,新节点也有childrenupdateChildren(oldVnode.elm, oldVnode.children, newVnode.children);} else {// 老的没有children,新的有childrenoldVnode.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节点还是vnodeif (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.keyreturn {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()参数类型不正确')}}
本文链接地址:https://www.jiuchutong.com/zhishi/303747.html 转载请保留说明!

上一篇:Python如何进行进程间的通信(python 如何)

下一篇:前端 Git-Hooks 工程化实践(git web hook)

  • steam怎么用余额买游戏(steam怎么余额退款到微信)

    steam怎么用余额买游戏(steam怎么余额退款到微信)

  • 抖音取消关注对方能收到信息吗(抖音取消关注对方会收到提示吗)

    抖音取消关注对方能收到信息吗(抖音取消关注对方会收到提示吗)

  • 微信字数限制多少字(微信字数限制多少字5461)

    微信字数限制多少字(微信字数限制多少字5461)

  • 华为手机怎样下载拼多多app到手机上(华为手机怎样下载app软件)

    华为手机怎样下载拼多多app到手机上(华为手机怎样下载app软件)

  • 彩信视频可以发几秒(彩信视频发送限制)

    彩信视频可以发几秒(彩信视频发送限制)

  • 怎样去除抖音水印号(怎样去除抖音水印不留痕迹的视频)

    怎样去除抖音水印号(怎样去除抖音水印不留痕迹的视频)

  • 苹果x还原所有设置会怎样(苹果x还原所有设置系统会还原吗)

    苹果x还原所有设置会怎样(苹果x还原所有设置系统会还原吗)

  • vivo手机横屏失灵(vivo手机横屏就黑屏)

    vivo手机横屏失灵(vivo手机横屏就黑屏)

  • 抖音小店需要多少保证金(抖音小店需要多少人)

    抖音小店需要多少保证金(抖音小店需要多少人)

  • 笔记本待机多久算正常(笔记本待机多久?)

    笔记本待机多久算正常(笔记本待机多久?)

  • 打印机灰度开还是关(打印机灰度打印需要消耗彩色墨吗)

    打印机灰度开还是关(打印机灰度打印需要消耗彩色墨吗)

  • 抖音多少粉丝可以挂小黄车(抖音多少粉丝可以放小黄车)

    抖音多少粉丝可以挂小黄车(抖音多少粉丝可以放小黄车)

  • 办事通实名认证不了怎么办

    办事通实名认证不了怎么办

  • 笔记本保修期内维修要钱吗(笔记本保修期内屏幕坏了需要收费吗)

    笔记本保修期内维修要钱吗(笔记本保修期内屏幕坏了需要收费吗)

  • 电池毫安大小的区别(电池毫安越大越耐用么)

    电池毫安大小的区别(电池毫安越大越耐用么)

  • 淘宝拍单什么意思(淘宝拍单违法吗)

    淘宝拍单什么意思(淘宝拍单违法吗)

  • 苹果11pro几个卡(苹果11pro几张电话卡)

    苹果11pro几个卡(苹果11pro几张电话卡)

  • 苹果x怎么关闭黑暗模式(苹果x怎么关闭专注模式)

    苹果x怎么关闭黑暗模式(苹果x怎么关闭专注模式)

  • 天猫积分怎么换购物券(天猫积分怎么换88VIP)

    天猫积分怎么换购物券(天猫积分怎么换88VIP)

  • 微信静音是对方听不见(微信静音对方能看到吗)

    微信静音是对方听不见(微信静音对方能看到吗)

  • 如何在电脑上看抖音直播(如何在电脑上看配置)

    如何在电脑上看抖音直播(如何在电脑上看配置)

  • 快手直播为啥不上同城(快手直播为啥不进人)

    快手直播为啥不上同城(快手直播为啥不进人)

  • 钉钉如何考勤打卡(钉钉如何考勤打卡设置)

    钉钉如何考勤打卡(钉钉如何考勤打卡设置)

  • 电脑怎么下载爱思助手(电脑怎么下载爱奇艺软件到桌面)

    电脑怎么下载爱思助手(电脑怎么下载爱奇艺软件到桌面)

  • 什么是coms  coms设置图解(什么是comsol)

    什么是coms coms设置图解(什么是comsol)

  • SSE:后端向前端发送消息(springboot SseEmitter)(sse后端向前端推送 前端只能收到 末尾的值)

    SSE:后端向前端发送消息(springboot SseEmitter)(sse后端向前端推送 前端只能收到 末尾的值)

  • 增值税专用发票怎么开
  • 一般纳税人季报怎么申报
  • 财务软件怎么看利润
  • 小规模纳税人出租
  • 个税申报人员多怎么申报
  • 个体户可以开多个银行账户吗
  • 会计凭证辅助项
  • 公司股权关系怎么填写
  • 贸易类公司做产品代理账务如何处理
  • 公关费用明细表
  • 应交增值税减免税额
  • 领用包装物会计处理
  • 劳务派遣差额征税会计分录
  • 外贸企业出出口退税会计科目怎么做账?
  • 商品按照售价金额核算法进行日常核算
  • 企业所得税递增是什么意思
  • 预收款没有发票怎么入账
  • 政府补助专款专用如何入账
  • ipad常用语在哪里
  • 支付离退休人员退休金可以用现金结算吗
  • opencv详解
  • 外购产品视同自产产品办理免抵退税的条件有哪几种
  • 国家最高科技奖2018
  • vue调用后端接口的方法
  • 企业接受现金捐赠如何开具发票
  • 职工工伤住院费用记什么科目
  • 第十四届蓝桥杯大赛官网
  • 完美解决显卡利用率低
  • 吉野是哪里
  • 蒙特城堡干红葡萄酒价格
  • 哪些收入不列入交纳党费计算基数
  • 划拨建设用地使用权没有使用期限的限制
  • 牛客前端刷题怎么样
  • ie11已经为了帮助保护您的计算机而关闭此网页
  • apt-key is deprecated
  • 在税务局怎么查补缴的社保
  • 增值税发票名称可以写个人吗
  • 销售货物免税
  • 不是自己生产的产品可以用自己的商标吗
  • 个人所得税app怎么查询个人所得税
  • 外账是代理记账吗
  • 会计怎么实现财务自由
  • mysql命令行如何命令行导出查询结果并导入
  • 图片加载不存在
  • rbac权限管理设计
  • 材料采购成本计算表
  • 出口货物离岸价差异原因说明表在电子税务局的位置
  • 纳税人具有怎样的特征?
  • 报废车怎么处理最划算
  • 以前年度损益调整结转到本年利润吗
  • 核定征收的三种方式
  • 个人转让不动产交印花税吗
  • 出口退税免抵退税额要交城建税吗
  • 简易征收计算企业所得税怎么算
  • 销售做不动 应该做什么
  • 非金融企业之间借款账务处理
  • 专用发票怎么入账
  • sql存储过程实例
  • mysql 创建root用户和普通用户及修改删除功能
  • ubuntu磁盘空间突然满了
  • win键有啥用
  • 英伟达更新有用吗
  • 破解ssh端口
  • 电脑删除ie后怎么恢复
  • 64位win10预览版10565更新补丁KB3105208后蓝屏怎么办?
  • ubuntu的安装步骤
  • windows8正在准备windows
  • linux虚拟机怎么调出命令行
  • win7睡眠唤醒设置密码
  • css教程实例
  • css条件语句
  • css控制图片置灰
  • chrome heat
  • js array insert
  • js能实现的简单效果
  • 怎么运用知识点
  • 工会经费税务代收
  • 甘肃省网上税务登记流程
  • 云阅卷查询成绩登录入口
  • 房产交契税网上可以交吗
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设