Skip to content

❤聊聊vue2和vue3的区别

面试官:说说vue2和vue3的区别

总结:

v2和v3的区别主要表现在以下方面

1、双向数据绑定原理

v2和v3的双向数据绑定的原理上,Vue2使用的是ES5 的一个 API【Object.defineProperty】,通过发布/订阅模式实现的。但是对于对象新增和删除的属性只能$set和$delete来操作。

Vue3使用的是ES6的Proxy对数据进行代理,能够监听对象里面新增和删除的属性的操作,直接弥补了$set和$delete的作用。

2、组件生命周期的变化

Vue2 的生命周期钩子主要包括 beforeCreatecreatedbeforeMountmounted 等。
Vue3 对生命周期进行了重命名和组合,使用了setup以及onbeforeMountonmounted等来替代。

并且vue3为了方便开发者在渲染和响应式系统调试,新增了 onRenderTrackedonRenderTriggered 两个钩子。可以精准地追踪到是哪个响应式依赖触发了组件的重新渲染,这在 Vue2 中是无法直接通过生命周期钩子实现的。

3、API 设计风格

Vue2使用的是Options API(选项式 API),通过 datamethodsmounted 等选项来组织代码,这种写法逻辑复杂时容易导致时代码分散,难以维护。
Vue3 引入了 Composition API(组合式 API),使用 setup 函数配合响应式 API,可以将相关的业务逻辑聚合在一起,提高了代码的复用和维护。

4、对 TypeScript 的支持。

Vue2 基于 Flow 进行类型检查,对 TypeScript 的支持不够。
Vue3 源码使用 TypeScript 重写,提供了完美的类型推断,在编写代码时能获得更好的智能提示和类型安全保障。

5、碎片化Fragments

Vue2中,template下只允许存在一个根节点

Vue3中可以有多个根节点,为我们创建一个虚拟的Fragment节点。

6、v-for 和 v-if 的区别

在vue2中,v-for优先级 > v-if,所以v-for与 v-if 可以同时用。

Vue3中正好相反 v-if的优先级 > v-for,会报一个当前v-if判断的变量还没有被定义的错误。

7、新增组件Teleport和Suspense

Vue3新增了Teleport传送门组件和Suspense异步加载状态组件,vue2之中我们组件只能嵌套在#app内部,vue3之中Teleport的to属性可以把我们的组件,比如modal组件渲染到任意的外部Dom上。

8、Tree-Shaking(摇树优化)

Tree-Shaking 指的就是当我们引入一个模块的时候,只引入需要的代码而不引入所有代码。

是用于打包构建时的优化,解决的是代码体积过大问题。

原理是静态分析代码,找出哪些导出的模块在项目中从未被使用过,然后在打包时将这些“死代码”剔除掉。就像摇动一棵树,把枯黄的树叶摇下来。

Vue2中很多全局API(比如Vue.nextTick)或者组件(如 <transition>)都挂载在核心的 Vue 对象上。即使我们没用到,但js的动态特性让打包工具不敢轻易删掉它,导致 Vue2 的打包体积很难缩小(基础包大概 20KB+ gzip)。

9、Diff 算法的优化。

Vue2 在进行虚拟DOM 对比时,采用全量对比的方式,即使节点是静态的也会参与比较,性能开销大。

Vue3引入了静态标记(Patch Flags)和静态提升。

静态提升:对于纯静态的节点(如 <div>Hello</div>),V3在编译时会将其提升到渲染函数外部,只创建一次,更新时直接复用,避免重复创建,不参与Diff。借此来提升渲染性能。

静态标记(Patch Flags):编译器在编译模板时,给带有动态绑定的节点打上标记,比如TEXTCLASSPROPS,Diff过程中只对比带有标记的节点,并且根据标记的类型进行精准对比(比如标记了 CLASS,就只对比 class 属性,不管其他属性)。

1、 v2和v3双向数据绑定原理

双向数据绑定的原理不同: Vue2使用的是ES5 的一个 API【Object.defineProperty】Object.defineProperty,通过发布/订阅实现

Vue3使用的是ES6的Proxy,对数据进行代理,能够监听对象和数组的变化

proxy的优势如下

  1. defineProperty只能监听某个属性,不能对全对象监听
  2. 可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
  3. 可以监听数组,不用再去单独的对数组做特异性操作 vue3.x可以检测到数组内部数据的变化

典型的就是this.$set()没有必要使用了!

2、生命周期

Vue2的生命周期钩子函数 beforeCreate、created、beforeMount、mounted等 Vue3的生命周期钩子函数 setup、onBeforeMount、onMounted等 并且提供了两个调试的钩子onRenderTrackedonRenderTriggered

Vue2和Vue3的生命周期

Vue 2 生命周期钩子Vue 3 生命周期钩子
beforeCreatesetup()
createdsetup()
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
activatedonActivated
deactivatedonDeactivated

Vue2

TIP

  • beforeCreate: 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
  • created: 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
  • beforeMount: 在挂载开始之前被调用:相关的 render 函数首次被调用。
  • mounted: el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
  • beforeUpdate: 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
  • updated: 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
  • beforeDestroy: 实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed: Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
  • activated: keep-alive 组件激活时调用。
  • deactivated: keep-alive 组件停用时调用。

Vue3

TIP

  • setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
  • onBeforeMount() : 组件挂载到节点上之前执行的函数。
  • onMounted() : 组件挂载完成后执行的函数。
  • onBeforeUpdate(): 组件更新之前执行的函数。
  • onUpdated(): 组件更新完成之后执行的函数。
  • onBeforeUnmount(): 组件卸载之前执行的函数。
  • onUnmounted(): 组件卸载完成后执行的函数

若组件被<keep-alive></keep-alive>包含,则多出下面两个钩子函数。

- onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行 。
- onDeactivated(): 比如从 A组件,切换到 B 组件,A 组件消失时执行。

3、API风格

Vue2使用的是选项式API,里面有data、methods、mounted等

Vue3使用的是组合式API,使用的是setup函数

分别简单写出vue2和vue3的东西我们可以发现如下:

Vue2

JS
<template>
  <div>
    <h2>{{ title }} </h2>
  </div>
</template>

<script>
export default {
  components: {}, // 组件
  props: {
    title: String
  },
  data() {
    return {
      title: 'Vue2'
    };
  },
  mounted() {
    // 在组件挂载时执行的代码
  },
  methods: {
    // 方法定义
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName;
    }
  }
};
</script>

Vue3

js
<template>
  <div>
    <h2> {{title}}</h2>
  </div>
</template>
<script>
import {ref,reactive} from 'vue'
export default {
    components:{}, //组件
    props:{title:String}, 
    setup(){
      // 数据
      const state=reactive({
        userName:'',
        phone:conmputed(()=>{})
      });

      const numbertotal=()=>{
        // 计算数字总和
      };
      return {
        state,
        numbertotal,
      }
    }
}
</script>

直接使用setup函数可以写成如图:

JS
<script setup>
import { onMounted } from 'vue'; // 使用前带引入生命周期钩子

onMounted(() => {
  // 逻辑1
});

onMounted(() => {
  // 逻辑2
});
</script>

4、兼容性

支持TypeScript

Vue3源码使用TypeScript重写,完全支持TypeScript,提供了完美的类型推断,提高项目的可维护性

Vue2对TypeScript的支持有限

5、支持碎片化((Fragments))多根节点

Vue2中,template下只允许存在一个根节点

Vue3中可以有多个根节点,为我们创建一个虚拟的Fragment节点。

Vue2

JS
<template>
  <div>
    <h2> { title} </h2>
  </div>
</template>

Vue3

JS
<template>
  <div></div>
  <h2> { title} </h2>
</template>

6、v-for 和 v-if 的区别

在vue2中v-for与 v-if 可以同时用,【优先级】:v-for > v-if

v-for的优先级比v-if高,所以可以一起用, 但是每次页面渲染的时候都会重复的进行判断是十分消耗性能的,不推荐使用

因为v-if和v-for在同一层级,Vue在渲染组件的时 先根据v-for遍历所有 数据 并将他们都生成对应的虚拟DOM,之后再根据v-if的判断去对不符合条件的 元素进行从虚拟DOM的删除

这里为什么不推荐显而易见: 就是因为 会先执行v-for循环遍历出所有的数据 但是这个时候不是先进行v-if的 判断而是会去先对所有的数据都生成对应的虚拟DOM 然后再去通过v-if的判断 去 去掉不符合条件的虚拟DOM 这样就会造成性能的消耗 生成了没必要的虚拟 DOM元素 并且还对这些没必要的虚拟DOM元素进行了一次删除大大提高了 性能的损耗

Vue3中正好相反 v-if > for 【优先级】 v-if > v-for

所以在Vue3中想要把v-if和v-for一起放在标签内一起使用是不可能的会直接报

原因:在Vue3中的v-if是比v-for的优先级高的 所以会先执行v-if但是v-if 的
执行又需要依靠到v-for的数据 但是这里是先执行v-if这个时候v-for还没有遍
历数据 所以会报一个当前v-if判断的变量还没有被定义的错误

解决方案
1:嵌套使用
2:使用computed计算属性或提前对数组进行filter过滤操作

(1)嵌套使用:

我们把优先级高的指令放到内层嵌套的形式去写也就是在Vue3中我们外层 使用v-for 内层去使用v-if判断 这样就可以保证v-for先执行把数据都遍历出来供 我们的v-if使用 并且Vue可以先根据v-for遍历出来的所有子元素 去渲染只符合 v-if判断条件的元素将符合条件的渲染并且生成虚拟DOM,而不符合条件的不会生成虚拟DOM

这样可以避免不必要的性能消耗 提高页面渲染的速度

为什么使用嵌套不会产生额外的虚拟DOM元素呢 ? 因为在这里的嵌套是父子级 的关系 v-if是v-for的子级也就是说在v-for循环出来所有的数据之后 就会到子级 去执行生成对应子级虚拟DOM的任务 但是在子级的元素标签上有v-if的判断 不符合的不会渲染 所以 不符合的元素的虚拟DOM就不会生成了

(2)我们可以避免同时使用v-if和v-for

我们首先明确我们的目的 如果遇到了这种需要同时使用的时候一般是因为 我们的数组还没有经过处理 所以需要遍历数组的同时对数组中的值进行处理 所以我们可以先一步对数组进行处理后 再进行遍历 例如我们可以通过数组的filter方法提前过滤 或者放到computed 计算属性中提前处理好 只使用一个v-for去遍历通过computed计算属性过滤好的元素。 ————————————————

7、Vue3新增组件

Vue3新增了Teleport传送门组件和Suspense异步加载状态组件

Teleport传送门组件

你写的组件挂载到任何你想挂载的DOM上

弹窗组件:

js
<template>
  <teleport>
    <div id="center">
      <h2><slot>this is a modal</slot></h2>
      <button @click="buttonClick">Close</button>
    </div>
  </teleport>
</template>

<script>
export default {
  props: {
    isOpen: Boolean
  },
  emits: ['close-modal'],
  setup(props, context) {
    const buttonClick = () => {
      context.emit('close-modal');
    };

    return {
      buttonClick
    };
  }
};
</script>

<style>
#center {
  width: 200px;
  height: 200px;
  border: 2px solid black;
  background: white;
  position: fixed;
  left: 50%;
  top: 50%;
  margin-left: -100px;
  margin-top: -100px;
}
</style>

在app.vue中使用的时候跟普通组件调用 vue2你可能只能嵌套在#app中

但是vue3之中,Teleport可以把modal组件渲染到任意你想渲染的外部Dom上,不必嵌套在#app中

(举例) 可将弹窗 移动到 #app中之外的位置

使用to属性去移动到想要的地方:

JS
<template>
  <button @click="dialogVisible = true">显示弹窗</button>
  
  <teleport to="body">
    <div class="dialog" v-if="dialogVisible">
      我是弹窗,我直接移动到了 body 标签下
    </div>
  </teleport>
</template>

异步组件(Suspense)

允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading ,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。

8、 Tree-Shaking(摇树优化)

Tree Shaking 指的就是当引入一个模块的时候,只引入需要的代码而不引入这个模块的所有代码

官方标准的说法:

Tree-shaking的本质是消除无用的js代码。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination)

webpack 里面自带 Tree Shaking 这个功能来

在 webpack 项目中,有一个入口文件,相当于一棵树的主干,入口文件有很多依赖的模块,相当于树枝。实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 Tree-Shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。

在Vite项目中,Tree-shaking是默认启用的,只需确保在代码中只引入实际使用的模块即可

实际代码

我们可以明显看出vue3采取的命名导出的方式更加具体细致,按需引入

JS
// Vue2 方式(无法 Tree-shaking)
import Vue from 'vue'
import VueRouter from 'vue-router'
import Vuex from 'vuex'

Vue.use(VueRouter)
Vue.use(Vuex)

// Vue3 方式(支持 Tree-shaking)
import { createApp } from 'vue'
import { createRouter } from 'vue-router'
import { createStore } from 'vuex'

// 只创建实际使用的功能
const app = createApp({})
const router = createRouter({})
const store = createStore({})

Tree-Shaking的原理是静态分析代码,找出哪些导出的模块在项目中从未被使用过,然后在打包时将这些“死代码”剔除掉。就像摇动一棵树,把枯黄的树叶摇下来。

Vue2 的痛点: Vue2 的架构设计导致它很难进行 Tree-Shaking。很多全局 API(如 Vue.nextTick、Vue.set)和组件(如 <transition>)都挂载在核心的 Vue 对象上。即使你的项目根本没有用到 <transition>,由于 JavaScript 的动态特性,打包工具不敢轻易删掉它,导致 Vue2 的打包体积很难缩小(基础包大概 20KB+ gzip)。

Vue3 的改进: Vue3 全面拥抱了 ES6 模块,将全局 API 改为了具名导出。

js
// Vue2: 无法 Tree-Shaking,打包工具不知道运行时会不会用 Vue.transition
import Vue from 'vue';
Vue.nextTick(() => {});

// Vue3: 具名导入,如果没用到 nextTick,打包时直接丢弃
import { nextTick } from 'vue';
nextTick(() => {});

这使得 Vue3 的基础包体积可以非常小(大概 10KB- gzip),真正做到了“按需引入”。

9、Diff算法的优化

  1. 核心概念 Diff 算法是虚拟 DOM 更新的核心。当数据变化时,Vue 会生成新的虚拟 DOM 树,与旧的虚拟 DOM 树进行对比,找出差异,然后只更新差异部分的真实 DOM。Diff 算法的优化,就是为了让这个对比过程更快、更精准。

  2. Vue2 vs Vue3 的表现 richang Vue2 的全量 Diff: Vue2 的 Diff 算法是全量对比。当组件内的数据发生变化时,会生成一棵完整的新虚拟 DOM 树,然后与旧树进行逐层、逐节点对比。即使组件的某些子节点是静态的(永远不会变),也会被强制对比一遍,这造成了不必要的性能浪费。

Vue3 的静态标记与快速 Diff: Vue3 引入了基于编译时提示的优化策略,核心是**“只对比动态节点”**: 静态提升: 对于纯静态的节点(如 <div>Hello</div>),Vue3 在编译时会将其提升到渲染函数外部,只创建一次,更新时直接复用,不参与 Diff。 PatchFlag(静态标记): 编译器在编译模板时,会给带有动态绑定的节点打上标记(如 TEXT、CLASS、PROPS)。在 Diff 时,Vue3 只对比有标记的节点,并且根据标记的类型精准对比(比如标记了 CLASS,就只对比 class 属性,不管其他属性)。 Block Tree(块级树): Vue3 会将组件的根节点或 v-if/v-for 节点作为一个 Block,Block 内部只收集动态节点到一个数组中。Diff 时直接遍历这个动态节点数组,跳过所有静态节点,实现了从“树级别”到“数组级别”的降维打击。

⑨定义数据变量

Vue2中数据放在data里,方法放在methods里

Vue3在setup方法里定义数据,这个方法在组件初始化的时候触发,使用reactive和ref将数据变成响应式数据

Vue2

JS
<script>
export default {
  components: {}, // 组件
  props: {
    title: String
  },
  data() {
    return {
      title: 'Vue2'
    };
  }
};
</script>

Vue3

在Vue3.0,使用一个新的setup()方法,此方法在组件初始化构造的时候触发。

JS
<script setup>
import { reactive } from 'vue';

const state = reactive({
  username: '',
  password: ''
});

defineProps({
  title: String
});

return { state };
</script>

Released under the MIT License.