返回顶部
首页 > 资讯 > 前端开发 > JavaScript >浅谈Vue DIFF
  • 949
分享到

浅谈Vue DIFF

Vue DIFF 2023-05-19 09:05:35 949人浏览 薄情痞子
摘要

目录不带 key 的操作带 key 的操作简单 DIFFVue是如何找到需要进行移动的元素Vue是如何移动元素的Vue是如何进行新增元素的Vue 是如何删除多余的旧元素的双指针 DI

不带 key 的操作

对于不带 key 的情况,我们的操作略显原始,整个流程如下:

  • 首先获取 oldChildren 和 newChildren 的长度
  • 如果说两者一样长的话

就从 tag/children 逐个进行比对,如果相同,则进行保留,如果不同,则进行删除,并且重新进行挂载。

  • 如果说两者不一样长的话

如果 newChildren 更长,就在进行如上操作之后,再从 newChildren 多处的部分开始,逐个进行添加。

如果 oldChildren 更长,就在进行如上操作之后,再从 oldChildren 多出的部分开始,逐个进行删除。

代码如下:

const patch = (n1, n2) => {
    if(n1.tag !== n2.tag) {
      const n1ElParent = n1.el.parentElement;
      n1ElParent.removeChild(n1.el);
      mount(n2, n1ElParent)
    } else {
      // 取出 element 对象,并且在 n2 中进行保存
      const el = n1.el = n2.el
      // 为了获取 value
      const oldProps = n1.props || []
      const newProps = n2.props || []
      // 遍历新对象
      for(const key in newProps) {
        const oldValue = oldProps[key]
        const newValue = newProps[key]
        if(oldValue != newValue) {
          if(key.startsWith("on")) {
            el.addEventListener(key.slice(2).toLowerCase(), newValue);
          } else {
            el.setAttribute(key, newValue);
          }
        }
      }
      // 删除旧的 props
      for(const key in oldProps) {
        if(key.startsWith("on")) {
          const value = oldProps[key];
          // removeEventListener(type, listener)
          // type ==> 一个字符串,表示需要移除的事件类型 | listener ==> 需要从目标事件中移除的事件处理函数。
          el.removeEventListener(key.slice(2).toLowerCase(), value)
        } 
        if(!(key in newProps)) {
          el.removeAttribute(key)
        }
      }
      // 3. 处理children
      const oldChildren = n1.children || []
      const newChildren = n2.children || []
      // 如果新的 node 的 children 只是一个字符串,那直接覆盖就可以了
      if (typeof newChildren === 'string') {
        // 如果旧的也是字符串,那就判断一下是不是一样的,然后覆盖一下
        if (typeof oldChildren === 'string') {
          if (newChildren != oldChildren) {
            el.textContent = newChildren
          }
        } else {
          // 如果新的是很多很多东西,我直接覆盖就可以了
          el.innerhtml = newChildren
        }
      } else {
        // 这种情况新的 node 并不是字符串,是数组啥的
        // 然后判断一下旧的的情况
        // 如果是字符串
        if(typeof oldChildren === 'string') {
          el.innerHTML = ""
          newChildren.forEach((item) => {
            mount(item, el)
          })
        } else {
          // 如果都不是字符串的话
          // 拿到共同长度
          const commonLength = Math.min(oldChildren.length, newChildren.length)
          for(let i=0;i<commonLength;i++) {
            patch(oldChildren[i], newChildren[i])
          }
          // 如果新的节点的子节点多一些的话,那就全部挂载上去
          if(newChildren.length > oldChildren.length) {
            newChildren.slice(commonLength).forEach((item) => {
              mount(item, el)
            })
          }
          // 如果旧的节点的子节点多一些,那就一个个删除!
          if(newChildren.length > oldChildren.length) {
            oldChildren.slice(commonLength).forEach((item) => {
              el.removeChild(item)
            })
          }
        }
      }
    }
  }

带 key 的操作

这个就比较灵性了。

简单 DIFF

首先我们还是正常进行比对

我们开两个 for 循环,外层是 newChildren ,内层是 oldChildren,如果两者的 key 相同,我们就可以进行到下一步了 ==> 找到需要进行移动的元素。

Vue是如何找到需要进行移动的元素

我们会创建一个 lastIndex 参数,用来标记 key 相同的情况的最大的 Index,大致逻辑是:

if (j < lastIndex) {
  // 进行 patch 操作
} else {
  lastIndex = i
}

所以在我们的 j 小于 lastIndex 的时候,我们就知道,该移动了~

Vue是如何移动元素的

我们知道 j(旧的vnode Index) < lastIndex 的 case 我们要进行移动

我们首先要拿到 newChildren[i - 1](新 vnode 的 i - 1 的值),当前新节点的前一个 vnode,然后进行移动,最后直接 insert 就好了。

Vue是如何进行新增元素的

我们会创建一个 find 参数,用来判断这个节点是不是新节点

如果是旧节点,我们就走移动逻辑

如果是新节点,我们就在后面新增,这里有一个问题,就是我加在谁的后面

如果 newChildren[i - 1] 为 null,我们就直接挂载到第一个节点上

如果 newChildren[i - 1] 不为 null,我们就把新节点挂载到 newChildren[i - 1] 上

Vue 是如何删除多余的旧元素的

我们遍历完新元素之后,还需要遍历一遍旧元素,如果说当前旧元素的 key 在新元素中无对应,那就摘出来,删除它。

双指针 DIFF

流程如下:

  • 首先是 oldStart <- newStart
  • 然后是 oldEnd <- newEnd
  • 然后是 oldEnd <- newStart
  • 最后是 newEnd <- newStart

这样看起来是没有问题的,但是如果没有一个匹配到呢,那是不是就有问题啦?

在这种情况下,我们应该用 newStart 的 key 和 oldChildren 中的所有 key 做比较,得到 idx ,如果 idx > 0 ,则我们就做移动,然后把这个位置设置为 undefined,后面如果碰到了这个位置就直接跳过去。

快速 DIFF

快速 DIFF 做了一个预处理,把相同的东西提前打补丁处理了。

分别从前往后开循环以及从后往前开循环,把相同的处理掉,不同的留下来。

如何新增?

image.png

如图所示,如果我们前后两轮循环遍历完之后,newEnd <= j,就说明有新增,我们找到 oldEnd,while(j >= newEnd)在它的后面一个接一个的挂载进去就可以了。

如何删除?

image.png

如图所示,如果我们前后两轮循环遍历完之后,oldEnd <= j,就说明有新增,我们找到 oldEnd,while (j <= oldEnd) 一路删除上去。

我们刚才看到的两种都是理想情况,那如果预处理之后情况依旧复杂呢?

image.png

function patchKeyedChildren(n1, n2, container) { 
    const newChildren = n2.children
    const oldChildren = n1.children
    // 更新相同的前置节点
    // 省略部分代码
    // 更新相同的后置节点
    // 省略部分代码
    if (j > oldEnd && j <= newEnd) { 
        // 省略部分代码 
    } else if (j > newEnd && j <= oldEnd) { 
        // 省略部分代码 
    } else {
        // 增加 else 分支来处理非理想情况
    }
}

这个时候我们再开一个 source 数组

source 数组将用来存储新的一组子节点中的节点在旧的一组子节点中的位置索引,后面将会使用它计算出一个最长递增子序列,并用于辅助完成 DOM 移动的操作

由于之前我们要寻找两个 key 相等的值需要使用两个 for 循环,这样就会导致时间复杂度相当的高,所以我们在这里使用了一个 key-index[新数组的索引]的映射表,这样我们只需要做一次便利就可以了,不需要去做嵌套,时间复杂度从 O(N^2) -> O(N)。

image.png

如何判断是否移动?

和之前简单 DIFF 的流程很像,之前我们是找最大索引值,如果索引小的都需要移动,索引更大则更新 lastIndex

我们在讲解简单 Diff 算法时曾提到,如果在遍历过程中遇到的索引值呈现递增趋势,则说明不需要移动节点,反之则需要。

image.png

所以这里的 moved 为 true 我们就可以移动了。

如何移动元素通过 source 找到最长递增子序列

通过最长递增子序列得到一个子序列索引的数组,最长递增子序列不需要移动

image.png

然后做处理,对于新节点,也就是 source 中是 -1 的情况,我们直接新增就好了,如果不是新节点,我们就判断两者是否相同,相同则移动最长递增子序列中的索引,不同则移动 source 中的索引。

以下是对于索引中两者不相同的处理。

image.png

到此这篇关于浅谈Vue DIFF的文章就介绍到这了,更多相关Vue DIFF内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 浅谈Vue DIFF

本文链接: https://lsjlt.com/news/212665.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 浅谈Vue DIFF
    目录不带 key 的操作带 key 的操作简单 DIFFVue是如何找到需要进行移动的元素Vue是如何移动元素的Vue是如何进行新增元素的Vue 是如何删除多余的旧元素的双指针 DI...
    99+
    2023-05-19
    Vue DIFF
  • 简单谈谈Vue中的diff算法
    目录概述 虚拟Dom(virtual dom) 原理 实现过程 patch方法 sameVnode函数 patchVnode函数 updateChildren函数 结语 概述 di...
    99+
    2024-04-02
  • 浅谈Vue中的this.$store.state.xx.xx
    目录Vue this.$store.state.xx.xx获取store中的数据我的项目文件结构vue项目都在什么时候用store.state、$store.state和this.$...
    99+
    2024-04-02
  • 浅谈VUE uni-app 常用API
    目录一、路由与页面跳转二、界面总结一、路由与页面跳转 uni.navigateTo(OBJECT) 保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返...
    99+
    2024-04-02
  • 浅谈vue的生命周期
    目录1.什么是生命周期有什么作用2.第一次加载页面会触发哪几个钩子3.简述每个周期应用于哪个场景4.created和mounted的区别5.vue在哪个生命周期获取数据总结1.什么是...
    99+
    2024-04-02
  • 浅析Vue中Virtual DOM和Diff原理及实现
    目录0. 写在开头1. vdom2. Diff0. 写在开头 本文将秉承Talk is cheap, show me the code原则,做到文字最精简,一切交由代码说明! 1. ...
    99+
    2023-03-21
    Vue Virtual DOM Diff原理 Vue Virtual DOM Vue Diff
  • 浅谈Vue插槽实现原理
    目录一、样例代码二、透过现象看本质三、实现原理四、父组件编译阶段五、父组件生成渲染方法六、父组件生成VNode七、子组件状态初始化八、子组件编译阶段九、子组件生成渲染方法十、使用技巧...
    99+
    2024-04-02
  • 浅谈VUE uni-app 核心知识
    目录规范 a. 页面文件遵循vue单文件组件规范b. 组件标签靠近小程序规范c. 接口能力(JS API)靠近微信小程序规范e. 数据绑定及事件处理使用Vue.js 规范特色 a. ...
    99+
    2024-04-02
  • 浅谈VUE uni-app 开发环境
    目录1.通过 HBuilderX 可视化界面 2.通过 vue-cli 命令执行总结1.通过 HBuilderX 可视化界面 a. 创建uni-app; b. 运行uni-app...
    99+
    2024-04-02
  • 浅谈VUE uni-app 模板语法
    1.v-bind(简写 :) 组件属性中要使用data中定义的数据变量,或组件属性要使用表达式,需用v-bind指定 简写 : 2.v-on(简写@) 监听DOM事件 cli...
    99+
    2024-04-02
  • 浅谈VUE uni-app 基础组件
    1 .scroll-view 使用竖向滚动时,需要给 一个固定高度,通过 css 设置 height;使用横向滚动时,需要给添加white-space: nowrap;样式。 s...
    99+
    2024-04-02
  • 浅谈VUE uni-app 生命周期
    目录一、应用的生命周期二、页面生命周期三、组件生命周期总结一、应用的生命周期 onLaunch 当uni-app 初始化完成时触发(全局只触发一次)onShow 当 uni-ap...
    99+
    2024-04-02
  • 浅谈vue首次渲染全过程
    目录1、vue初始化vue入口文件完整版和运行时版本的区别1.1、src/core/instace/index.js1.2、src/core/index.js1.3、src/plat...
    99+
    2024-04-02
  • 浅谈VUE uni-app 自定义组件
    1.父组件向子组件传递数据可以通过 props 2.子组件向父组件传递数据可以通过自定义事件,父组件自定义事件,子组件触发父组件的事件,并传传递数据 3.子组件可以定义插槽slot,...
    99+
    2024-04-02
  • 浅谈Vue+AntDesignform表单的一些坑
    目录设置默认值的坑自定义 v-decorator 组件的坑最近在用 vue + ant 写项目发现 from 组件的坑还是比较多的 设置默认值的坑 控制台报 Warning: You...
    99+
    2024-04-02
  • 浅谈Vue入门需掌握的知识
    Vue作为一款目前最流行的前端框架之一,是许多前端开发工程师的不二选择。最近我在前端岗位上也运用Vue实现了几款产品,那么今天来分享一下Vue是什么,以及我对Vue的见解。 一、定义...
    99+
    2024-04-02
  • 深入浅析React中diff算法
    React中diff算法的理解 diff算法用来计算出Virtual DOM中改变的部分,然后针对该部分进行DOM操作,而不用重新渲染整个页面,渲染整个DOM结构的过程中开销是很大的...
    99+
    2024-04-02
  • 浅谈Vue的组件间传值(包括Vuex)
    目录父传子:子传父:在不使用Vuex的情况下,组件间传值的方式是通过父传子的方式或者兄弟组件传值。 父传子: fatherComponent: <template> ...
    99+
    2024-04-02
  • 浅谈vue 移动端完美适配方案
    前言:根据最近做的一个医疗手机端项目总结在移动端,vue怎么在不同屏幕上做根据不同屏幕大小适配 1、适配方案 在本项目中我所使用的vue移动方案是使用amfe-flexible ...
    99+
    2024-04-02
  • 浅谈vue-cli5关于yarn的一个小坑
    目录问题解决方案原因问题 昨天有小伙伴下了我的 DEMO之后反映运行报错。 因为这个项目环境我测试过许多次,不管是npm还是yarn都能正常运行,所以听到运行报错时下意识地就认为是...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作