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 相关文章推荐
javascript 选择文件夹对话框(web)
Jul 07 Javascript
JS动态创建Table,Tr,Td并赋值的具体实现
Jul 05 Javascript
用innerhtml提高页面打开速度的方法
Aug 02 Javascript
JS实现仿google、百度搜索框输入信息智能提示的实现方法
Apr 20 Javascript
javascript模拟评分控件实现方法
May 13 Javascript
JavaScript中函数(Function)的apply与call理解
Jul 08 Javascript
JavaScript判断IE版本型号
Jul 27 Javascript
node通过express搭建自己的服务器
Sep 30 Javascript
用vue封装插件并发布到npm的方法步骤
Oct 18 Javascript
vue中render函数的使用详解
Oct 12 Javascript
JS实现根据数组对象的某一属性排序操作示例
Jan 14 Javascript
在Vue中使用mockjs代码实例
Nov 25 Vue.js
微信小程序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的正则处理函数总结分析
2008/06/20 PHP
PHP session 会话处理函数
2016/06/06 PHP
PHP魔术方法之__call与__callStatic使用方法
2017/07/23 PHP
javascript 鼠标滚轮事件
2009/04/09 Javascript
基于Jquery的文字滚动跑马灯插件(一个页面多个滚动区)
2010/07/26 Javascript
javascript 常用功能总结
2012/03/18 Javascript
nodejs开发微博实例
2015/03/25 NodeJs
JS实现表单中checkbox对勾选中增加边框显示效果
2015/08/21 Javascript
JS实现网页标题随机显示名人名言的方法
2015/11/03 Javascript
js变量提升深入理解
2016/09/16 Javascript
JS+HTML5实现上传图片预览效果完整实例【测试可用】
2017/04/20 Javascript
Vue 兄弟组件通信的方法(不使用Vuex)
2017/10/26 Javascript
JS设计模式之策略模式概念与用法分析
2018/02/05 Javascript
react native 获取地理位置的方法示例
2018/08/28 Javascript
vue 循环加载数据并获取第一条记录的方法
2018/09/26 Javascript
详解vue中async-await的使用误区
2018/12/05 Javascript
简单了解vue.js数组的常用操作
2019/06/17 Javascript
解决layui-open关闭自身窗口的问题
2019/09/10 Javascript
Vue 打包体积优化方案小结
2020/05/20 Javascript
详解Howler.js Web音频播放终极解决方案
2020/08/23 Javascript
使用js和canvas实现时钟效果
2020/09/08 Javascript
vue 解决mintui弹窗弹起来,底部页面滚动bug问题
2020/11/12 Javascript
Python实现的金山快盘的签到程序
2013/01/17 Python
实例讲解python函数式编程
2014/06/09 Python
python中nan与inf转为特定数字方法示例
2017/05/11 Python
Python unittest 简单实现参数化的方法
2018/11/30 Python
python自定义函数实现最大值的输出方法
2019/07/09 Python
python实现的批量分析xml标签中各个类别个数功能示例
2019/12/30 Python
python如何调用java类
2020/07/05 Python
Python如何定义有默认参数的函数
2020/08/10 Python
深入探究HTML5的History API
2015/07/09 HTML / CSS
JD Sports比利时官网:英国领先的运动鞋和运动服饰零售商
2018/10/10 全球购物
Traffic People官网:女式花裙、上衣和连身裤
2020/10/12 全球购物
客服专员岗位职责
2014/02/28 职场文书
小学生国庆节演讲稿
2014/09/05 职场文书
送给教师们,到底该如何写好教学反思?
2019/07/02 职场文书