vue页面更新patch的实现示例


Posted in Javascript onMarch 25, 2020

patch的流程

组件页面渲染时,将render返回的新vnode(新节点)和组件实例保存的vnode(旧节点)作为参数,调用patch方法,更新DOM。

判断两个节点是否相同

处理过程中,需要判断节点是否相同。相同节点需要满足以下条件:

  • key相同
  • 标签类型相同
  • 注释节点标识相同,都是注释节点,或者都不是注释节点
  • data的值状态相同,或者都有值,或者都没值
function sameVnode (a, b) {// 判断两个VNode节点是否是同一个节点
  return (
    a.key === b.key && // key相同
    (
      a.tag === b.tag && // tag相同
      a.isComment === b.isComment && // 注释节点标识相同
      isDef(a.data) === isDef(b.data) && // data值状态相同
      sameInputType(a, b) // input的type相同
    )
  )
}

patch方法

patch判断流程如下:

a) 如果新节点为空,此时旧节点存在(组件销毁时),调用旧节点destroy生命周期函数

b) 如果旧节点为空,根据新节点创建DOM

c) 其他(如果新旧节点都存在)

  • a) 旧节点不是DOM(组件节点),且新旧节点相同
    • 执行patchVnode
  • b) 旧节点是DOM元素或者两个节点不相同
    • 创建新节点DOM,销毁旧节点以及DOM。
function patch (oldVnode, vnode, hydrating, removeOnly) {
  if (isUndef(vnode)) {
   if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
   return
  }
  ...
  if (isUndef(oldVnode)) {
   isInitialPatch = true;// 组件初始加载
   createElm(vnode, insertedVnodeQueue);
  } else {
   var isRealElement = isDef(oldVnode.nodeType);
   if (!isRealElement && sameVnode(oldVnode, vnode)) {
    patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
   } else {
    ...
    var oldElm = oldVnode.elm;
    var parentElm = nodeOps.parentNode(oldElm);// 获取父元素
    // create new node
    createElm(
     vnode,
     insertedVnodeQueue,
     oldElm._leaveCb ? null : parentElm,
     nodeOps.nextSibling(oldElm)// 获取紧跟的弟弟元素
    );
    if (isDef(parentElm)) {
     removeVnodes(parentElm, [oldVnode], 0, 0);// 销毁旧节点以及DOM元素
    } else if (isDef(oldVnode.tag)) {
     invokeDestroyHook(oldVnode);
    }
   }
  }
  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
  return vnode.elm
 }
}

patchVnode方法

当两个节点相同时,执行patchVnode方法。在处理各种情况之前,会将旧节点elm属性值赋值给新节点的elm属性,保持elm保持一致。

具体流程如下:

a)如果新旧节点完全相同(引用相同 oldVnode === vnode)

  • 直接返回不处理

b) 如果新节点不是文本节点

  • a)都存在子节点,新旧节点的子节点数组引用不同(oldCh !== ch)
    • updateChildren
  • b)新节点有子节点,旧节点没有
    • 1)查重子节点(key)
    • 2)如果旧节点是文本节点,先清空文本
    • 3)创建子节点DOM元素
  • c)旧节点有子节点,新节点没有
    • 移除子节点以及DOM
  • d)旧节点是文本节点
    • 清除文本
  • c)如果新节点是文本节点,并且和旧节点文本不相同
    • 则直接替换文本内容。
  • d)其他(新节点是文本节点,并且和旧节点相同)
    • 不处理
function patchVnode (
  oldVnode,
  vnode,
  insertedVnodeQueue,
  ownerArray,
  index,
  removeOnly
 ) {
  if (oldVnode === vnode) {
   return
  }
  ...
  if (isUndef(vnode.text)) {
   if (isDef(oldCh) && isDef(ch)) {
    if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
   } else if (isDef(ch)) {
    if (process.env.NODE_ENV !== 'production') {
     checkDuplicateKeys(ch);
    }
    if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); }
    addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
   } 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);
  }
  ...
 }

updateChildren方法

updateChildren方法处理相同新旧节点的子节点。方法定义了以下变量(updateChildren的节点都表示的是子节点):

var oldStartIdx = 0;// 表示当前正在处理的旧起始节点序号
  var 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, // 尚未处理的旧节点key值映射
    idxInOld, // 与新节点key值相同的旧节点序号
    vnodeToMove, // 与新节点key值相同的旧节点
    refElm;// 指向当前正在处理的新结尾节点的后一个节点(已处理)的DOM元素

根据新旧节点的对比结果,更新DOM元素,此过程并不改变新旧节点的排序。序号指向正在处理的节点,分别是新旧节点的起始和结尾节点。对比过程以新起始节点为主导,对比方向是由两侧向中间。优先比对新旧节点的起始节点和结尾节点,再查找与新起始节点相同的且未处理的旧节点。当旧节点全部处理完(旧起始和结尾序号重叠),此时新节点可能未处理完,就添加新节点DOM元素。当新节点全部处理完(新起始和结尾序号重叠),可能存在旧节点,就删除旧节点DOM元素。

具体流程如下:

新旧子节点的起始序号不大于结尾序号时,执行以下流程:

a)如果旧子节点两侧存在undefined节点

  • 旧起始节点undefined,oldStartVnode = oldCh[++oldStartIdx]
  • 旧结尾节点undefined,oldEndVnode = oldCh[--oldEndIdx]

b)新旧子节点的起始节点相同(前后比较)

  • patchVNode更新DOM内容
  • oldStartVnode = oldCh[++oldStartIdx]
  • newStartVnode = newCh[++newStartIdx]

c)新旧子节点的结尾节点相同(前后比较)

  • patchVNode更新DOM内容
  • oldEndVnode = oldCh[--oldEndIdx]
  • newEndVnode = newCh[--newEndIdx]

d)旧起始节点和新结尾节点相同(前后比较)

  • patchVNode更新DOM内容
  • 将旧起始节点DOM添加到旧结尾节点DOM前面
  • oldStartVnode = oldCh[++oldStartIdx]
  • newEndVnode = newCh[--newEndIdx]

e)旧结尾节点和新起始节点相同(前后比较)

  • patchVNode更新DOM内容
  • 将旧结尾节点DOM添加到旧起始节点DOM前面
  • oldEndVnode = oldCh[--oldEndIdx]
  • newStartVnode = newCh[++newStartIdx]

f)其他(缓存尚未处理的旧节点key值,依此判断旧节点中是否存在和新起始节点相同的节点)

  • a)尚未处理的旧节点中不存在与新起始节点相同的节点
    • 创建新节点DOM并添加到旧起始节点DOM的前面
    • newStartVnode = newCh[++newStartIdx]
  • b)旧节点中存在与新起始节点key相同的节点
    • a)旧节点中存在与新起始节点相同的节点
      • patchVode
      • 将相同的旧节点DOM添加到旧起始节点DOM前面
      • 将相同的旧节点置为undefinedoldCh[idxInOld] = undefined
      • newStartVnode = newCh[++newStartIdx]
    • b)key相同,但标签类型不同的节点
      • 创建新节点DOM并添加到旧起始节点DOM的前面
      • newStartVnode = newCh[++newStartIdx]

循环结束

a)如果旧节点遍历完(oldStartIdx > oldEndIdx

  • 把剩余未处理新节点DOM添加到上一个新结尾节点DOM前面(从新起始节点到新结尾节点,都未处理过)

b)如果新节点遍历完(newStartIdx > newEndIdx

  • 移除旧起始和结尾节点以及他们之间的节点的DOM(从旧起始节点到旧结尾节点,可能存在处理过的节点,但处理过已被置为undefined)
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
  var oldStartIdx = 0;// 表示当前正在处理的旧起始节点序号
  var 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, idxInOld, vnodeToMove, refElm;
  ...
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
   if (isUndef(oldStartVnode)) {
    oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
   } else if (isUndef(oldEndVnode)) {
    oldEndVnode = oldCh[--oldEndIdx];
   } else if (sameVnode(oldStartVnode, newStartVnode)) {
    patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
    oldStartVnode = oldCh[++oldStartIdx];
    newStartVnode = newCh[++newStartIdx];
   } else if (sameVnode(oldEndVnode, newEndVnode)) {
    patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx);
    oldEndVnode = oldCh[--oldEndIdx];
    newEndVnode = newCh[--newEndIdx];
   } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
    patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx);
    canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
    oldStartVnode = oldCh[++oldStartIdx];
    newEndVnode = newCh[--newEndIdx];
   } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
    patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
    canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
    oldEndVnode = oldCh[--oldEndIdx];
    newStartVnode = newCh[++newStartIdx];
   } else {
    if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); }// 缓存尚未处理的旧节点key值
    idxInOld = isDef(newStartVnode.key)
     ? oldKeyToIdx[newStartVnode.key]
     : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
    if (isUndef(idxInOld)) { // New element
     createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
    } else {
     vnodeToMove = oldCh[idxInOld];
     if (sameVnode(vnodeToMove, newStartVnode)) {
      patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
      oldCh[idxInOld] = undefined;
      canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
     } else {
      // same key but different element. treat as new element
      createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
     }
    }
    newStartVnode = newCh[++newStartIdx];
   }
  }
  if (oldStartIdx > oldEndIdx) {
   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
  } else if (newStartIdx > newEndIdx) {
   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
  }
 }

updateChildren的示例:

1.左边表示新旧节点,节点下面标识起始和结尾节点(即正在处理的节点)。右边表示当前的DOM。

vue页面更新patch的实现示例

2.新节点的起始和结尾节点与旧节点的起始和结尾节点互不相同,并且在旧节点中未找到与新起始节点(新节点f)相同的节点。
所以创建节点f的DOM并添加到旧起始节点(旧节点a)DOM的前面,然后新起始节点序号加1,表示新节点f已处理,当前正在处理新起始节点c。

vue页面更新patch的实现示例

3.新节点的起始和结尾节点与旧节点的起始和结尾节点互不相同,但在旧节点中找到与新起始节点(节点c)相同的节点。
所以将旧节点c的DOM添加到旧起始节点(旧节点a)DOM的前面,旧节点c置空,然后新起始节点序号加1,表示新节点c已处理,当前正在处理新起始节点e。

vue页面更新patch的实现示例

4.新起始节点(新节点e)和旧结尾节点(旧节点e)相同。更新旧节点e的DOM内容,并将旧节点e的DOM移动到旧起始节点(旧节点a)DOM的前面,旧结尾节点序号减1,新起始节点加1,表示新旧节点e已处理,当前正在处理的是新起始节点g和旧结尾节点d。

vue页面更新patch的实现示例

5.新结尾节点(新节点d)和旧结尾节点(旧节点d)相同。仅更新旧节点d的DOM内容。新结尾节点序号减1,旧结尾节点序号减1,表示新旧节点d已处理,当前正在处理的是新结尾节点g和旧结尾节点c。由于旧节点c为空,则旧结尾节点为b。

vue页面更新patch的实现示例

6.新节点的起始和结尾节点与旧节点的起始和结尾节点互不相同,并且在旧节点中未找到与新起始节点(新节点g)相同的节点。
所以创建节点g的DOM并添加到旧起始节点(旧节点a)DOM的前面,然后新起始节点序号加1,表示新节点g已处理,当前正在处理新起始节点d。

vue页面更新patch的实现示例

7.由于新起始和结尾节点序号重叠,新节点已经处理完毕,存在尚未处理的旧节点,则移除未处理的旧节点DOM。

vue页面更新patch的实现示例

8.结束,最终的DOM。

vue页面更新patch的实现示例

到此这篇关于vue页面更新patch的实现示例的文章就介绍到这了,更多相关vue 更新patch内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
js判断选择时间不能小于当前时间的示例代码
Sep 24 Javascript
IE8中动态创建script标签onload无效的解决方法
Dec 22 Javascript
谷歌浏览器调试JavaScript小技巧
Dec 29 Javascript
jQuery实现Select左右复制移动内容
Aug 05 Javascript
JS类的定义与使用方法深入探索
Nov 26 Javascript
AngularJS打开页面隐藏显示表达式用法示例
Dec 25 Javascript
BootStrap Table后台分页时前台删除最后一页所有数据refresh刷新后无数据问题
Dec 28 Javascript
Angular+Node生成随机数的方法
Jun 16 Javascript
jquery.validate.js 多个相同name的处理方式
Jul 10 jQuery
详解自定义ajax支持跨域组件封装
Feb 08 Javascript
Promise.all中对于reject的处理方法
Aug 01 Javascript
Vue项目中使用jsonp抓取跨域数据的方法
Nov 10 Javascript
微信小程序scroll-view隐藏滚动条的方法详解
Mar 25 #Javascript
微信小程序点击item使之滚动到屏幕中间位置
Mar 25 #Javascript
微信小程序scroll-view点击项自动居中效果的实现
Mar 25 #Javascript
微信小程序 flexbox layout快速实现基本布局的解决方案
Mar 24 #Javascript
安装多版本Vue-CLI的实现方法
Mar 24 #Javascript
js实现页面图片消除效果
Mar 24 #Javascript
js实现消灭星星(web简易版)
Mar 24 #Javascript
You might like
php与php MySQL 之间的关系
2009/07/17 PHP
ThinkPHP使用心得分享-ThinkPHP + Ajax 实现2级联动下拉菜单
2014/05/15 PHP
PHP实现求解最长公共子串问题的方法
2017/11/17 PHP
Smarty模板语法详解
2019/07/20 PHP
laravel接管Dingo-api和默认的错误处理方式
2019/10/25 PHP
基于thinkphp5框架实现微信小程序支付 退款 订单查询 退款查询操作
2020/08/17 PHP
laravel中Redis队列监听中断的分析
2020/09/14 PHP
IE与Firefox在JavaScript上的7个不同写法小结
2009/09/14 Javascript
jquery随意添加移除html的实现代码
2011/06/21 Javascript
IE与Firefox在JavaScript上的7个不同句法分享
2011/10/30 Javascript
setinterval()与clearInterval()JS函数的调用方法
2015/01/21 Javascript
jQuery的bind()方法使用详解
2015/07/15 Javascript
JavaScript多并发问题如何处理
2015/10/28 Javascript
JQuery zClip插件实现复制页面内容到剪贴板
2015/11/02 Javascript
security.js实现的RSA加密功能示例
2018/06/06 Javascript
Vue列表渲染的示例代码
2018/11/01 Javascript
如何实现双向绑定mvvm的原理实现
2019/05/28 Javascript
JS实现页面数据懒加载
2020/02/13 Javascript
Vue的props父传子的示例代码
2020/05/20 Javascript
vue-cli3配置favicon.ico和title的流程
2020/10/27 Javascript
JavaScript实现原型封装轮播图
2020/12/27 Javascript
[01:00:12]2018DOTA2亚洲邀请赛 4.7 淘汰赛 VP vs LGD 第一场
2018/04/09 DOTA
Python画图学习入门教程
2016/07/01 Python
Python实现文件内容批量追加的方法示例
2017/08/29 Python
Python实现对百度云的文件上传(实例讲解)
2017/10/21 Python
Python多版本开发环境管理工具介绍
2019/07/03 Python
将自己的数据集制作成TFRecord格式教程
2020/02/17 Python
python读取yaml文件后修改写入本地实例
2020/04/27 Python
Linux中如何用命令创建目录
2015/01/12 面试题
经典优秀毕业生求职信范文分享
2013/12/18 职场文书
运动会广播稿30字
2014/01/21 职场文书
工作过失检讨书
2014/02/23 职场文书
2014党的群众路线教育实践活动总结报告
2014/10/31 职场文书
学习经验交流会总结
2015/11/02 职场文书
Redis Cluster集群动态扩容的实现
2021/07/15 Redis
Python实现PIL图像处理库绘制国际象棋棋盘
2021/07/16 Python