Vue2 - diff 原理(动图演示)

这篇具有很好参考价值的文章主要介绍了Vue2 - diff 原理(动图演示)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1,diff

解释:对比新旧虚拟DOM树,完成对真实DOM的更新,这个对比差异的过程叫做 diff

Vue 会在内部的 patch 函数中完成该过程。

diff 的时间点

当组件创建时,或依赖的数据变化时,会运行一个特定的函数来做2件事:

  • 运行 _render 函数生成新的 VNode tree(虚拟DOM树)
  • 运行 _update 函数,传入新的 VNode tree 的根节点,对比新旧2个树,最终完成对真实DOM的更新。

代码表示大致逻辑:

// vue构造函数
function Vue(){
  // ... 其他代码
  var updateComponent = () => {
    this._update(this._render())
  }
  new Watcher(updateComponent);
  // ... 其他代码
}

diff 就发生在_update函数的运行过程中

Watcher 的作用:简单来说,运行传入的函数(updateComponent),对函数中用到的响应式数据进行依赖收集。

Watcher 的作用具体参考Vue2-数据响应式原理

2,_update 函数

  1. _update 函数接收一个 VNode 参数,也就是this._render() 返回的生成的虚拟 DOM 树。

  2. _update 函数通过当前组件的 this._vnode 属性,拿到的虚拟 DOM 树。

  3. _update 函数首先会给组件的 this._vnode 属性重新赋值,让它指向新树。再判断旧树是否存在:

    • 不存在,说明是第一次加载组件,则通过 patch 函数直接遍历新树,为每个节点生成真实的DOM,并挂载到每个节点的 elm 属性上。(虚拟节点通过 elm 属性指向绑定的真实DOM。)
    • 存在,说明之前已经渲染过组件,则通过 patch 函数对新旧树对比,来实现2个目标:
      • 完成对所有真实 DOM 的最小化处理。
      • 让新树的节点对应合适的真实DOM。

不存在的流程:
Vue2 - diff 原理(动图演示),vue2,前端,vue,diff,数据响应式,vue.js
存在时的流程:
Vue2 - diff 原理(动图演示),vue2,前端,vue,diff,数据响应式,vue.js

// 伪代码表示:
function update(vnode) { // 参数是新 vnode
  const oldVnode = this._vnode
  this._vnode = vnode
}

这样就完成了组件的虚拟DOM树的更新

但还需要解决真实的 DOM 更新(如果不考虑效率,直接用新树生成真实DOM即可)。而为了提升效率,需要对比新旧树,通过实现下面2个目标来提升效率。这个步骤在 _patch 函数中实现。

  • 完成对所有真实 DOM 的最小化处理。
  • 让新树的节点对应合适的真实DOM。

3,_patch 函数(进行 diff)

先来介绍几个术语,方便后续阅读:

  1. 【相同】:指2个虚拟节点的标签(tag)类型、key 值均相同。input 元素还需要考虑 type 属性。

不考虑内容,或后代节点。

<!-- 举例 -->

<!-- 节点相同 -->
<h1>123</h1> <!-- 对应节点 { tag: h1, key: undefined } -->
<h1>456</h1> <!-- 对应节点 { tag: h1, key: undefined } -->

<!-- 节点相同 -->
没有标签包裹的文字1  <!-- 对应节点 { tag: undefined , key: undefined } -->
没有标签包裹的文字2  <!-- 对应节点 { tag: undefined , key: undefined } -->

<!-- 节点相同 -->
<h1>123</h1> <!-- 对应节点 { tag: h1, key: undefined } -->
<h1>456</h1> <!-- 对应节点 { tag: h1, key: undefined } -->

<!-- 节点不同 -->
<input type="text" key="_key1"> <!-- 对应节点 { tag: input, key: _key1, data: {attrs: {type: text}} } -->
<input type="radio" key="_key1"> <!-- 对应节点 { tag: input, key: _key1, data: {attrs: {type: radio}} } -->
  1. 【新建元素】:根据一个虚拟节点提供的信息,创建一个真实的 DOM 元素,同时挂载到虚拟节点的 elm 属性上。

  2. 【销毁元素】:运行 vnode.elm.remove()

  3. 【更新】:2个虚拟节点进行对比更新,仅发生在2个虚拟节点【相同】的情况下。

  4. 【对比子节点】:对2个虚拟节点的子节点进行对比。

3.1,根节点比较

Vue2 - diff 原理(动图演示),vue2,前端,vue,diff,数据响应式,vue.js
首先会对根节点比较,如果2个虚拟节点

【相同】:进入【更新】流程

  • 将旧节点的真实 DOM 赋值到新节点:newVNode.elm = oldVNode.elm
  • 对比新旧节点的属性,有变化的更新到真实 DOM 中。
  • 当前2个节点处理完毕,开始【对比子节点】

不【相同】:新节点递归的【新建元素】。旧节点直接【销毁元素】。

如果根节点都不相同,则没有对比的必要,直接当做旧树不存在处理。

3.2,子节点比较

diff 的重点

再说明下 diff 的目的:为了修改真实的 DOM,并和新的 VNode tree 对应上

在【对比】子节点时,vue 的实现思路:

  1. 尽量什么也不做。
  2. 不行的话,尽量只改动元素属性。
  3. 还不行,尽量移动元素,而不是删除或创建元素。
  4. 还不行,删除和创建元素。

实现大致逻辑:使用头尾指针+遍历来实现。动图演示(数字代表的是 key,蓝块中的数字代表真实DOM的内容):

Vue2 - diff 原理(动图演示),vue2,前端,vue,diff,数据响应式,vue.js

  • 对比新旧指针
    • 一样则进入【更新】流程。
      • 顺序:新旧头指针,新旧尾指针,旧头和新尾,旧尾和新头。
    • 不一样,则以新头指针为基准,看对应的 key 在旧树中是否存在(通过遍历旧树的方式),
      • 存在则进入【更新】流程,并调整真实 DOM 的位置,移动新头指针。
      • 不存在,则创建节点对应的 DOM 元素,
  • 当新头指针超过新尾指针,循环结束。剩下的旧节点如果还是正常的,说明没有处理完,则遍历销毁所有节点对应的真实DOM。旧树不用管,会被垃圾回收。

注意,每个新旧节点【更新】时,都会递归的遍历子节点。

4,key的问题

举例1

for 循环中的 item 如果不使用 key,数据更新(尤其是位置发生了变化)后做 diff 时,会认为原来位置新旧头指针每次指向的虚拟节点都【相同】,则每个节点都会【更新】。如果子节点较多,效率就更低了。

举例:

<template>
  <div>
    <ul>
      <li v-for="item in arr" :key="item">{{ item }}</li>
    </ul>
    <button @click="arr.unshift(99 + count++)">头部插入</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [1, 2, 3, 4, 5],
      count: 0,
    };
  },
};
</script>

因为 key 的存在,

  • 翻转数组时,也只是位置的移动,不会对比内容更新。
  • 头部插入时,只创建一个DOM,其他的DOM不做变动。否则逐一对比更新。

key 的效果:

Vue2 - diff 原理(动图演示),vue2,前端,vue,diff,数据响应式,vue.js

不加 key 的效果:

Vue2 - diff 原理(动图演示),vue2,前端,vue,diff,数据响应式,vue.js

举例2

注意,v-if/v-else 关于 key 的问题,vue3 会自动添加,可以看这篇文章对比 vue2 和 vue3 的变化。

<template>
  <div>
    <div>
      <span @click="isAccoutLogin = true">账号登录</span>
      <span>|</span>
      <span @click="isAccoutLogin = false">手机号登录</span>
    </div>
    <div v-if="isAccoutLogin" key="1">
      <label>账号</label>
      <input type="text" />
    </div>
    <div v-else key="2">
      <label>手机号</label>
      <input type="text" />
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isAccoutLogin: true,
    };
  },
};
</script>

不加 key 时,因为节点相同,子节点也相同,所以不做更新。

Vue2 - diff 原理(动图演示),vue2,前端,vue,diff,数据响应式,vue.js

key 才会有所区分,而清空输入框。

Vue2 - diff 原理(动图演示),vue2,前端,vue,diff,数据响应式,vue.js


以上。文章来源地址https://www.toymoban.com/news/detail-776413.html

到了这里,关于Vue2 - diff 原理(动图演示)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue2的生命周期详解(代码演示+源码)

            生命周期是指从开始创建、初始化数据、编译模版、挂载 Dom - 渲染、更新 - 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期,它主要强调一个时间段。用一句话来概括就是: Vue实例的生命周期: 从创建到销毁的整个过程 Vue框架内置函数,随着组件的生命周期阶

    2024年02月04日
    浏览(48)
  • Jeecg开发框架前端VUE2数据页面与后端数据库交互实现

    ​ JeecgBoot 是一款基于代码生成器的 低代码 开发平台,零代码开发!采用前后端分离架构:SpringBoot2.x,Ant DesignVue,Mybatis-plus,Shiro,JWT。强大的代码生成器让前后端代码一键生成,无需写任何代码! JeecgBoot引领新的开发模式(Online Coding模式- 代码生成器模式- 手工MERGE智能开发

    2024年02月11日
    浏览(41)
  • 前端学习---vue2--选项/数据--data-computed-watch-methods-props

    写在前面: vue提供了很多数据相关的。 简单的说就是进行双向绑定的区域。 vue实例的数据对象,会把data的数据转换成getter和setter,从而可以进行响应式的变化, vue实例创建后,可以通过vm.$data.x获取data里面的x,但同时vue实例也代理了其中的对象,所以我们一般使用简单的

    2024年02月14日
    浏览(39)
  • 【前端vue面试】vue2

    computed 有缓存,基于响应式依赖数据(基于data中声明过或者父组件传递的props中的数据)发生改变,才会重新进行计算 数据变,直接会触发相应的操作 watch监听引用类型,需要添加deep:true深度监听,拿不到oldVal(旧值),因为新值和老值指针相同。 v-show 和v-if 都是做条件隐

    2024年02月08日
    浏览(38)
  • 前端开发攻略---从源码角度分析Vue3的Propy比Vue2的defineproperty到底好在哪里。一篇文章让你彻底弄懂响应式原理。

    Vue的响应式到底要干什么? 无非就是要知道当你 读取 对象的时候,要知道它读了。要做一些别的事情 无非就是要知道当你 修改 对象的时候,要知道它改了。要做一些别的事情 所以要想一个办法, 把读取和修改的动作变成一个函数 ,读取和修改的时候分别调用对应的函数

    2024年04月17日
    浏览(46)
  • 【前端框架】Vue2合集

    1、Vue概念 vue 是一个用于构建用户界面的渐进式框架,由数据驱动 vue 的两种使用方式 vue 核心包开发:局部模块改造 vue 核心包与 vue 插件 工程化开发:整站 开发 2、 创建实例 1、准备容器 2、导包 3、创建Vue实例 4、指定配置项 = 渲染数据 3、插值表达式 插值表达式语法:

    2024年01月19日
    浏览(48)
  • 从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷

    内容 链接 从Vue2到Vue3【零】 Vue3简介 从Vue2到Vue3【一】 Composition API(第一章) 从Vue2到Vue3【二】 Composition API(第二章) 从Vue2到Vue3【三】 Composition API(第三章) 从Vue2到Vue3【四】 Composition API(第四章) 从Vue2到Vue3【五】 新的组件(Fragment、Teleport、Suspense) 从Vue2到Vue3【六

    2024年02月15日
    浏览(45)
  • 前端学习--vue2--2--vue指令基础

    写在前面: 前置内容 - vue配置 vue指令只的是带有v-前缀的特殊标签属性 插值表达式{{}}是一种vue模板语法。 利用表达式进行差值,渲染到页面。 表达式可以是被求值的代码,js会计算 不能用差值表达式的 不存在的数据 {{data里面不存在的字段 js {{if}} 标签属性 span id=“

    2024年02月14日
    浏览(39)
  • 【VUE2】VUE2基础知识和原理--超详细--超简介--零基础(一)

    想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象 demo容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法 demo容器里的代码被称为【Vue模板】 Vue实例和容器是一一对应的 真实开发中只有一个Vue实例,并且会配合着组件一起使用 {{xxx}}是Vue的语法:插值

    2024年02月16日
    浏览(35)
  • 前端vue2 全局水印效果

    最近写项目遇到一个需求,全局显示水印,不管在哪个路由都要显示。 想要实现的效果: 新建shuiyin.js文件 main.js中全局注册

    2024年02月15日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包