首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >26届秋招前端面试题系列 | Vue篇

26届秋招前端面试题系列 | Vue篇

作者头像
爱学英语的程序媛
发布2026-09-15 11:16:07
发布2026-09-15 11:16:07
20
举报

哈喽小伙伴们大家好,今天我们来看看Vue相关的面试题,整理不容易,看完如果觉得对你有帮助的话欢迎给个三连支持哦~

一、基础使用:

1、Vue 生命周期(8 个钩子函数 + 执行时机,如 created 与 mounted 区别)

看了上面的图片,相信你已经有个大概的了解了。

对于一个vue组件来说,它的生命周期无非就四个:创建,挂载,更新,销毁。

在创建阶段:主要涉及到两个钩子函数:beforeCreate()和created()。前者表示实例刚被创建,数据观测(data observer)和事件配置尚未完成,无法访问 datamethods 等。而后者则是实例创建完成,已完成数据观测和事件配置,可访问 datamethods,但 DOM 尚未生成($el 不存在)。

创建结束之后:组件会进入挂载阶段,在这个阶段主要有下面两个钩子会被执行:beforeMount()和mounted()。前者用于在模板编译 / 挂载前,

$el 已存在但未挂载到页面。

而mounted阶段则表示模板编译 / 挂载完成,DOM 已生成,可访问 DOM 元素(如通过 $refs),常用作初始化操作(如请求数据、初始化插件)。

我们的DOM元素挂载到页面上之后,接下来我们就要通过JS逻辑去控制视图的更新,因此,我们会进入更新阶段。

那在更新阶段呢,也有两个钩子函数会执行,beforeUpdate()和updated()。前者表示数据更新后,DOM 重新渲染前调用,可获取更新前的 DOM 状态。后者表示DOM 重新渲染完成后调用,可获取更新后的 DOM 状态(避免在此修改数据,可能导致无限循环)

当一个组件完成了它的使命后,为了防止内存被一直占用,以免造成一些其他的问题,我们就要及时将组件销毁。

同样,在vue中,也有专门的钩子去销毁组件。分别是beforeDestroy()和destroyed()

其中,beforeDestroy()钩子会在实例销毁前调用,仍可访问实例属性和方法,常用于清理操作(如清除定时器、解绑事件)。

而destroyed()则是在实例销毁后调用,所有事件监听器和子实例已被移除。

2、指令

vue中的指令其实分为两种,一种是vue本身内置的指令,另一种是开发者自己编写的自定义指令。

先来看看Vue中自内置的指令。

这些指令通常以v-为前缀,作用于HTML元素,用于在模板中实现各种功能。

常见的有以下几个:

1)、v-text

代码语言:javascript
复制
<p v-text="message"></p>  <!-- 等价于 <p>{{ message }}</p> -->

2)、v-html

代码语言:javascript
复制
<div v-html="rawHtml"></div>  <!-- 渲染 HTML 字符串 -->

3)、v-bind

代码语言:javascript
复制
<img v-bind:src="imageUrl">
<a :href="linkUrl">链接</a>  <!-- 缩写形式 -->
<div :class="{ active: isActive }"></div>  <!-- 绑定类名 -->
<div :style="{ color: textColor, fontSize: '16px' }"></div>  <!-- 绑定样式 -->

上面三个是用于绑定数据的。

4)、v-on:用于绑定事件监听器

代码语言:javascript
复制
<button v-on:click="handleClick">点击</button>
<button @click="handleClick">点击</button>  <!-- 缩写形式 -->
<button @click="count++">计数器</button>  <!-- 直接执行表达式 -->
<form @submit.prevent="handleSubmit">提交</form>  <!-- 带修饰符 -->

常见的修饰符有四个:

.prevent:阻止默认行为(如表单提交)

.stop:阻止事件冒泡

.once:事件只触发一次

.enter:仅响应回车键(键盘事件)

5)、v-if

代码语言:javascript
复制
<p v-if="isShow">显示内容</p>
<p v-else-if="isLoading">加载中...</p>
<p v-else>隐藏内容</p>

6)、v-show

代码语言:javascript
复制
<p v-show="isVisible">始终存在于 DOM 中</p>

这两个主要用于条件渲染。

它俩的区别在于v-if是通过条件判断来决定销毁或者重建这个元素。而v-show则是通过控制元素的display属性来决定是否展示这个元素。

前者适合条件切换不频繁的场景,而 v-show 更适合频繁切换的场景。

3、组件通信

vue中组件通信的方式其实有很多种。

我们先来看父子组件之间的通信方式:

1)、父传子:父组件通过v-bind绑定数据,子组件通过props接收数据。

代码语言:javascript
复制
<!-- 父组件 -->
<template>
  <ChildComponent :message="parentMsg" :user="userInfo" />
</template>
<script>
export default {
  data() {
    return {
      parentMsg: "来自父组件的消息",
      userInfo: { name: "张三", age: 20 }
    };
  }
};
</script>
<!-- 子组件 ChildComponent -->
<template>
  <div>
    <p>{{ message }}</p>
    <p>{{ user.name }}</p>
  </div>
</template>
<script>
export default {
  // 声明接收的 props(可指定类型、默认值、验证等)
  props: {
    message: String,
    user: {
      type: Object,
      required: true
    }
  }
};
</script>

2)、子传父

  • 子组件通过 this.$emit('事件名', 数据) 触发自定义事件。
  • 父组件通过 v-on 监听事件并接收数据。
代码语言:javascript
复制
<!-- 子组件 ChildComponent -->
<template>
  <button @click="sendData">向父组件发送数据</button>
</template>
<script>
export default {
  methods: {
    sendData() {
      // 触发自定义事件,并传递数据
      this.$emit("child-event", "来自子组件的数据", 123);
    }
  }
};
</script>
<!-- 父组件 -->
<template>
  <ChildComponent @child-event="handleChildData" />
</template>
<script>
export default {
  methods: {
    handleChildData(data1, data2) {
      console.log(data1, data2); // 输出:"来自子组件的数据" 123
    }
  }
};
</script>

3)、父子组件双向绑定数据:通过v-model和sync来绑定和修改数据。

v-model:本质是 value 属性 + input 事件的语法糖,适用于表单类组件。

.sync:简化子组件修改父组件数据的写法(Vue 3 推荐用 v-model 替代)。

废话不多说,3,2,1,上代码!

代码语言:javascript
复制
<!-- 子组件 -->
<template>
  <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>
<script>
export default {
  props: ["modelValue"] // v-model 默认绑定的 prop 名
};
</script>
<!-- 父组件 -->
<template>
  <ChildComponent v-model="username" />
</template>
<script>
export default {
  data() { return { username: "" }; }
};
</script>

紧接着,我们来看一看兄弟组件之间的通信:

有两种方式:一种是通过父组件来共享数据,另外一种是通过事件总线来管理。

我们来看看通过事件总线是怎么来共享数据的。

代码语言:javascript
复制
// eventBus.js
import mitt from 'mitt';
export const bus = mitt();
// 组件 A(发布事件)
import { bus } from './eventBus';
bus.emit('share-data', '兄弟组件的数据');
// 组件 B(订阅事件)
import { bus } from './eventBus';
bus.on('share-data', (data) => {
  console.log(data); // 输出:"兄弟组件的数据"
});

那除了上面的两种方式之外,如果两个组件不是父子关系呢?那我们怎么通信。

vue也提供了顶层组件和底层组件之间的通信方式。

其中最常用的就是provide/inject。父组件通过 provide 提供数据,任意深层子组件通过 inject 获取数据(Vue 2 和 Vue 3 均支持),看了下面的代码你就知道了。

代码语言:javascript
复制
// 父组件(顶层)
export default {
  provide() {
    return {
      theme: "dark",
      changeTheme: (value) => { this.theme = value; } // 可传递方法
    };
  }
};
// 深层子组件
export default {
  inject: ["theme", "changeTheme"], // 注入数据
  mounted() {
    console.log(this.theme); // 输出:"dark"
    this.changeTheme("light"); // 调用父组件方法
  }
};

其实除了provide/inject之外,状态管理不也是一种跨层级组件之间通信的方式么?

当然,这种方式其实更加适合一些大型的项目,统一管理全局状态,任意组件可直接读写共享数据。同时,也会涉及到一些其他的依赖,看看是怎么实现的吧~

代码语言:javascript
复制
// store/counter.js(定义仓库)
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() { this.count++; }
  }
});
// 组件 A(修改状态)
import { useCounterStore } from './store/counter';
const store = useCounterStore();
store.increment();
// 组件 B(读取状态)
import { useCounterStore } from './store/counter';
const store = useCounterStore();
console.log(store.count); // 输出:1

二、核心原理:

1、数据响应式(Vue2 Object.defineProperty 缺陷、Vue3 Proxy 优势)

因为vue它是一个数据和视图双向绑定的框架。每当服务器返回的数据变化的时候视图也会对应的更新,那vue是怎么知道这些数据变化了呢?今天我们一起来看一看。

数据响应式的核心其实是追踪数据变化触发视图更新

  1. 当数据被读取时,记录「谁在使用这个数据」(依赖收集)。
  2. 当数据被修改时,通知「所有使用这个数据的地方」重新更新(触发更新)。

对于这一点,vue2和vue3的实现方式是不一样的。

Vue 2 通过 Object.defineProperty 对数据对象的属性进行劫持,来实现响应式。

看下面的代码:

代码语言:javascript
复制
function defineReactive(obj, key, value) {
  // 递归处理嵌套对象
  observe(value);

  Object.defineProperty(obj, key, {
    get() {
      // 依赖收集:记录当前使用该属性的组件
      console.log(`读取了 ${key} 的值:${value}`);
      return value;
    },
    set(newValue) {
      if (newValue !== value) {
        // 递归处理新值(如果是对象)
        observe(newValue);
        value = newValue;
        // 触发更新:通知依赖该属性的组件重新渲染
        console.log(`修改了 ${key} 的值:${newValue}`);
      }
    }
  });
}
// 对对象的所有属性进行响应式处理
function observe(obj) {
  if (typeof obj !== 'object' || obj === null) return;
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key]);
  });
}
// 测试
const data = { name: 'Vue 2', age: 5 };
observe(data);
data.name; // 触发 get:"读取了 name 的值:Vue 2"
data.name = 'Vue 2.0'; // 触发 set:"修改了 name 的值:Vue 2.0"

当时这种方式其实很多缺点。其一,Object.defineProperty 只能劫持已存在的属性,新增属性或删除已有属性时,无法触发更新。举个例子:

代码语言:javascript
复制
data.sex = 'male'; // 新增属性,不会触发响应式
delete data.age; // 删除属性,不会触发响应式

其二,但数组发生变化时,它也没有办法监听

对于数组,Object.defineProperty 只能劫持 length 和索引属性,但 Vue 2 没有对索引进行劫持(性能代价高),因此:

  • 直接通过索引修改数组元素(如 arr[0] = 1)不会触发更新。
  • 直接修改数组长度(如 arr.length = 0)不会触发更新。需通过数组变异方法(push/pop/splice 等,Vue 2 对这些方法进行了重写)才能触发响应式。

还有一个,就是涉及到性能的问题。当它对嵌套对象进行响应式处理时,需要递归遍历所有属性,深层嵌套对象会增加初始化成本。

那在vue2的基础上,vue3关于响应式的实现也做了很多改进。我们一起来看看vue3是怎么实现响应式的?

Vue 3 改用 Proxy 对整个对象进行代理,从根本上解决了 Vue 2 的缺陷。

我们来看详细的代码:

代码语言:javascript
复制
function reactive(obj) {
  if (typeof obj !== 'object' || obj === null) return obj;

  return new Proxy(obj, {
    get(target, key, receiver) {
      const value = Reflect.get(target, key, receiver);
      track(target, key); // 依赖收集
      // 对嵌套对象进行惰性代理
      return reactive(value);
    },
    set(target, key, newValue, receiver) {
      const oldValue = Reflect.get(target, key, receiver);
      if (oldValue !== newValue) {
        Reflect.set(target, key, newValue, receiver);
        trigger(target, key); // 触发更新
      }
    },
    deleteProperty(target, key) {
      const hadKey = Reflect.has(target, key);
      const result = Reflect.deleteProperty(target, key);
      if (hadKey && result) {
        trigger(target, key); // 删除属性时触发更新
      }
      return result;
    }
  });
}

总结一下:

1)、proxy代理的是整个对象,属性的增加和删除都能被监听到

2)、Proxy 对嵌套对象采用 “惰性代理”:只有当访问嵌套对象时,才会为其创建代理,避免了 Vue2 初始化时的深度遍历,对复杂对象更友好。

3)、Proxy 可以代理 ES6 新增的 MapSet 等集合类型,而 Object.defineProperty 只能处理普通对象。

对比一下:

特性

Vue2(Object.defineProperty)

Vue3(Proxy)

监听范围

仅已定义属性

整个对象(包括新增 / 删除属性)

数组支持

需重写原型方法,有局限

原生支持所有数组操作

嵌套对象处理

初始化时深度遍历

惰性代理(访问时才代理)

复杂数据结构

不支持 Map、Set 等

支持 Map、Set 等

性能

初始化成本高(深层对象)

初始化成本低,按需代理

可以看出来,vue3确实是做出了很大的改进。那你想一想:react是怎么处理的呢?为什么要这么处理?答案下期揭晓~

2、虚拟 DOM(原理 + diff 算法核心)

这里补充一些问题:

1)、什么是DOM?

2)、所谓的视图更新,归根结底更新的是啥?

3)、浏览器是怎么知道视图该更新了?

如果你的js基础比较扎实的话,上面三个问题你应该很清楚。

虚拟 DOM 它其实是对真实 DOM 的抽象表示,本质上是一个轻量级的 JavaScript 对象,用于描述 DOM 节点的结构和属性。

它的核心原理无非就这四个:

  1. 抽象表示:用 JS 对象模拟 DOM 树结构,包含标签名、属性、子节点等信息
  2. 状态映射:将应用状态映射到虚拟 DOM 对象
  3. 差异计算:通过对比新旧虚拟 DOM 树,找出差异(diff 算法)
  4. 批量更新:将所有差异一次性应用到真实 DOM,减少重绘重排

我们来看一个例子:

代码语言:javascript
复制
// 真实DOM
<div class="container">
  <h1>Hello World</h1>
  <p>Virtual DOM Demo</p>
</div>
// 对应的虚拟DOM表示
{
  tag: 'div',
  props: { class: 'container' },
  children: [
    { tag: 'h1', props: {}, children: ['Hello World'] },
    { tag: 'p', props: {}, children: ['Virtual DOM Demo'] }
  ]
}

上面的这些代码眼熟不?你知道它在浏览器的哪里吗?

3、模板编译过程

在回答这个问题之前,先来看一看什么是模板编译:

所谓模板编译,它就是将模板文件(包含静态内容和动态语法标记)转换为可执行代码(或中间代码),最终生成动态内容(如网页、文档、邮件等)的过程。其核心目标是将 “静态结构 + 动态逻辑” 分离的模板,高效转化为计算机可执行的指令,同时兼顾性能、安全性和可维护性。

模板编译通常分为 3 个核心阶段解析(Parsing)优化(Optimization)生成(Code Generation)

1. 第一阶段:解析(Parsing)—— 将模板转化为抽象语法树(AST)

解析是编译的 “读入” 环节,目标是将人类可读的模板文本,转化为计算机可理解的抽象语法树(Abstract Syntax Tree,AST)(一种描述代码结构的树形数据结构)。该阶段又分为两步:词法分析语法分析

(1)词法分析(Lexical Analysis):拆分 “模板 token”

词法分析器(Tokenizer/Lexer)会逐行扫描模板文本,将其拆分为一系列不可再分的最小语法单元(Token)。不同模板引擎的 Token 类型不同,常见类型如下:

  • 静态 Token:模板中的固定内容(如 HTML 标签、文本、空格),例如 <div class="box">Hello
  • 动态 Token:模板中的动态语法标记,例如: 变量标记:{{ username }}(Vue/Jinja2)、<%= name %>(EJS)。 逻辑标记:{% if isLogin %}(Jinja2)、v-if="show"(Vue 指令)。 循环标记:{% for item in list %}(Jinja2)、v-for="item in list"(Vue)。
(2)语法分析(Syntactic Analysis):构建 AST

语法分析器(Parser)会根据模板引擎的语法规则(如 “{{ 必须与 }} 配对”“if 必须有对应的 endif”),对 Token 序列进行校验和结构化,最终生成 AST。

AST 的每个节点对应模板的一个语法单元(如静态文本节点、变量节点、条件节点、循环节点),节点间通过父子关系体现模板的嵌套结构。

2. 第二阶段:优化(Optimization)—— 提升执行效率

优化阶段的目标是对 AST 进行改造,减少冗余计算、规避安全风险,提升最终代码的执行效率和安全性。该阶段在前端模板(如 Vue、React)中尤为重要,后端模板(如 Jinja2)优化相对简化。

常见的优化手段包括:

  • 静态节点提升:将不会随数据变化的静态节点(如固定 HTML 标签、静态文本)标记为 “静态”,避免每次数据更新时重复渲染(典型案例:Vue 的静态节点优化)。

例:<div class="header">Title</div> 是静态节点,编译后会被缓存,数据更新时不重新处理。

  • 表达式预计算:对模板中可提前计算的常量表达式进行预求值,减少运行时计算。

例:模板中的 {{ 1 + 2 * 3 }} 会被预计算为 {{ 7 }},运行时直接输出结果。

  • 安全过滤:对用户输入的变量(如

{{ username }})进行 HTML 转义(如将<转义为&lt;),防止 XSS 攻击(多数模板引擎默认开启,如 Jinja2、Vue)。

  • 死代码删除:移除 AST 中永远不会执行的节点(如

{% if false %}...{% endif %}中的内容)。

3. 第三阶段:生成(Code Generation)—— 将 AST 转化为可执行代码

生成阶段是编译的 “输出” 环节,目标是将优化后的 AST 转化为目标平台可执行的代码(如 JavaScript、Python、Java 字节码等),最终通过执行该代码生成动态内容。

不同模板引擎的 “目标代码” 类型不同,取决于其运行环境:

  • 前端模板(如 Vue、EJS):目标代码是JavaScript 函数(运行在浏览器中,接收数据后生成 DOM 片段)。
  • 后端模板(如 Jinja2、Django Template):目标代码是后端语言函数(如 Python 函数,运行在服务器端,接收数据后生成 HTML 字符串,返回给浏览器)。

4、vue中的状态管理:

Vue的状态管理主要有两种方案:一种是Vuex,另一种是Pinia

不管使用 Vuex 还是 Pinia,都围绕以下核心概念设计:

  • 状态(State):需要共享的数据(如 

userInfocartList)。

  • 修改(Mutations/Actions):修改状态的方法(同步操作 / 异步操作)。
  • 派生状态(Getters):基于状态计算得出的衍生数据(如购物车商品总数)。
  • 模块化(Modules):将状态按功能拆分(如用户模块、商品模块),避免单一状态树过于庞大。

Vuex和Pinia两者的核心思想是一致的,但 Pinia 语法更简洁,且原生支持 Vue 3 的 Composition API。

接下来我们以Pinia为例,详细介绍一下它是怎么管理状态的。

首先,安装Pinia:

代码语言:javascript
复制
npm install pinia

紧接着,创建Pinia实例并注入应用

代码语言:javascript
复制
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia() // 创建 Pinia 实例
app.use(pinia) // 注入应用
app.mount('#app')

第三步:定义store

Store 是保存状态和修改方法的容器,每个 Store 对应一个功能模块(如用户、购物车)。

代码语言:javascript
复制
// stores/cart.js(使用 Composition API 风格)
import { defineStore } from 'pinia'
// 定义并导出 Store,id 需唯一(用于 devtools 识别)
export const useCartStore = defineStore('cart', {
  // 状态:返回初始数据的函数
  state: () => ({
    list: [], // 购物车商品列表
    count: 0 // 商品总数(也可通过 getter 计算,此处为示例)
  }),
  // 派生状态:类似组件的 computed,依赖 state 生成新数据
  getters: {
    totalPrice: (state) => {
      // 计算购物车总价
      return state.list.reduce((sum, item) => sum + item.price * item.quantity, 0)
    }
  },
  // 方法:包含同步和异步操作,用于修改 state
  actions: {
    // 同步操作:直接修改 state
    addItem(item) {
      const existing = state.list.find(i => i.id === item.id)
      if (existing) {
        existing.quantity += 1
      } else {
        state.list.push({ ...item, quantity: 1 })
      }
      this.count += 1 // 可通过 this 访问 state 中的属性
    },
    // 异步操作:如从接口获取购物车数据
    async fetchCart() {
      const res = await fetch('/api/cart')
      const data = await res.json()
      this.list = data.list // 直接修改 state
      this.count = data.count
    }
  }
})

最后,在组件中使用store

代码语言:javascript
复制
<!-- CartComponent.vue -->
<template>
  <div>
    <p>商品总数:{{ cartStore.count }}</p>
    <p>总价:{{ cartStore.totalPrice }}</p>
    <button @click="handleAdd">添加商品</button>
  </div>
</template>
<script setup>
import { useCartStore } from '@/stores/cart'
// 获取 Store 实例
const cartStore = useCartStore()
// 调用 actions 中的方法
const handleAdd = () => {
  cartStore.addItem({ id: 1, name: '商品A', price: 99 })
}
// 组件挂载时加载购物车数据
cartStore.fetchCart()
</script>

Pinia的核心特性主要有以下几个:

1)、简化的 API:无需区分 mutations(同步)和 actions(异步),统一用 actions 处理所有操作。

2)、自动响应式:Store 中的 state 会自动转为响应式数据,组件使用时无需额外处理。

3)、模块化设计:每个 Store 就是一个独立模块,无需像 Vuex 那样嵌套 modules

4)、Composition API 支持:可直接在 setup 中使用,也支持 Options API(如 mapState)。

5)、开发工具集成:Pinia 与 Vue Devtools 深度集成,可追踪状态变化历史。

以上就是状态管理的内容。

三、Vue3 新特性:

1、Composition API(setup 函数、ref/reactive)

Vue 3 引入的 Composition API 是一套全新的逻辑组织和复用方案,相比 Vue 2 的 Options API(通过 datamethodscomputed 等选项组织代码),它更灵活,尤其适合处理复杂组件的逻辑。核心特性包括 setup 函数、ref/reactive 响应式 API 以及生命周期钩子的调整。

一、setup 函数:Composition API 的入口

setup 是组件初始化前执行的函数,是 Composition API 的核心入口,所有组合式 API 都在此函数内使用。

1. 执行时机
  • 在组件实例创建前执行(介于 beforeCreate 和 created 之间)。
  • 此时组件实例尚未完全初始化,this 指向 undefined(不能通过 this 访问组件选项)。
2. 参数

setup 接收两个参数:

  • props:组件接收的 props 数据(响应式,会随父组件传递的值更新)。
  • context:上下文对象,包含组件的核心属性和方法,如:
    • attrs:非 props 的属性集合(类似 

this.$attrs)。

  • slots:插槽内容(类似 

this.$slots)。

  • emit:触发自定义事件的方法(类似 

this.$emit)。

看下面的代码:

代码语言:javascript
复制
<template>
  <div>{{ message }}</div>
</template>
<script>
export default {
  props: {
    title: String
  },
  setup(props, context) {
    // 访问 props(响应式)
    console.log(props.title)
    // 触发事件
    context.emit('custom-event', 'data')
    // 定义响应式数据
    const message = 'Hello Composition API'

    // 返回值会暴露给模板和组件的其他选项
    return { message }
  }
}
</script>

在setup中返回的值(一般是对象)会直接暴露给模板,在模板中就可以直接使用这些对象的属性和方法。

在vue中,有两个响应式的API,分别是ref 和 reactive,它俩具体有什么区别,接下来我们一探究竟。

1. ref:处理基本类型与引用类型

ref 用于将基本类型(如字符串、数字、布尔值)或引用类型(对象、数组)转为响应式数据。

  • 内部会将数据包装为一个带 value 属性的对象。
  • 在模板中使用时,Vue 会自动解包(无需写 .value)。
  • 在脚本中访问 / 修改时,需通过 .value 操作。

看下面的这段代码:

代码语言:javascript
复制
<template>
  <div>
    <p>计数:{{ count }}</p>
    <button @click="increment">+1</button>
  </div>
</template>
<script>
import { ref } from 'vue'
export default {
  setup() {
    // 创建响应式数据(基本类型)
    const count = ref(0)
    // 方法(修改时需用 .value)
    const increment = () => {
      count.value++
    }
    return { count, increment }
  }
}
</script>

如果用 ref 包装对象 / 数组,内部会自动通过 reactive 转为响应式代理,脚本中修改时仍需用 .value

2. reactive:处理对象类型

reactive 用于将对象或数组转为响应式数据(基于 ES6 Proxy 实现)。

  • 只能用于对象 / 数组,不能用于基本类型。
  • 访问 / 修改时无需 .value,直接操作属性。

举个例子:

代码语言:javascript
复制
<template>
  <div>
    <p>姓名:{{ user.name }}</p>
    <p>年龄:{{ user.age }}</p>
    <button @click="user.age++">增长年龄</button>
  </div>
</template>
<script>
import { reactive } from 'vue'
export default {
  setup() {
    // 创建响应式对象
    const user = reactive({
      name: 'Alice',
      age: 20
    })
    return { user }
  }
}
</script>

reactive 有 “深层响应式” 特性,修改嵌套属性也会触发更新

同样都是响应式的API,我们怎么决定使用ref还是reactive呢?

对于基本类型,必须要使用ref

对于对象或者数组,两者都是可以的。

但是如果是简单对象的话,推荐使用reactive(语法更简洁)。

如果是可能被重新赋值的对象,此时推荐使用refreactive 无法直接替换整个对象)。

如果用错了,有些情况下响应式会失效,比如说下面这种情况。

代码语言:javascript
复制
// reactive 无法直接替换对象(会丢失响应式)
const user = reactive({ name: 'Alice' })
user = { name: 'Bob' } // 错误:响应式失效
// ref 可直接替换对象(保持响应式)
const user = ref({ name: 'Alice' })
user.value = { name: 'Bob' } // 正确

四、扩展:

1、对比 Vue2 与 Vue3 核心差异

最后,我们一起来看看Vue2 与 Vue3 有哪些区别?这个也是面试中非常容易被问到的一个点,包括我面试别人的时候也要去问这个问题。

首先就是关于响应式的实现原理,上面也介绍了,这里给大家看一张表格。

特性

Vue2

Vue3

实现原理

Object.defineProperty

Proxy + Reflect

数据类型支持

仅支持对象 / 数组

支持所有数据类型(对象、数组、基本类型等)

嵌套响应式

需递归遍历对象手动实现

天然支持深层响应式(Proxy 代理整个对象)

新增属性响应

需用 Vue.set(obj, key, val)

直接赋值即可(obj.newKey = val)

数组索引修改

不响应(需用 Vue.set 或变异方法)

直接响应(arr[0] = val 有效)

响应式触发时机

依赖属性 getter/setter 拦截

依赖 Proxy 陷阱(更全面的拦截能力)

 其次就是API的风格:在vue2中使用选项式API,通过 datamethodscomputed 等选项组织代码,逻辑分散在不同选项中。在vue3中使用组合式API,按逻辑相关性组织代码,支持逻辑复用。

第三,生命周期函数。

在Vue2中,以选项形式声明(如 mounted() {})。而在Vue3中,Composition API 中以函数形式导入使用(如 onMounted(() => {})),且部分钩子更名(如 beforeDestroy → onBeforeUnmount)。

除此之外,Vue3也提供了更多实用的模板特性。

例如v-model和setup语法糖。

第五就是状态管理。

Vue2:官方推荐 Vuex,需区分 mutations(同步)和 actions(异步),模块化需通过 modules 嵌套。

Vue3:官方推荐 Pinia(Vuex 的继任者),语法更简洁:

  • 无需区分 mutations 和 actions,统一用 actions 处理所有操作。
  • 天然支持 TypeScript,模块化更直观(每个 store 就是一个独立模块)。
  • 与 Vue3 的响应式系统深度集成,性能更好。

第六,性能优化:

  1. 包体积:通过 Tree-shaking 移除未使用的 API,核心包体积比 Vue2 减少约 40%。
  2. 初始化速度:比 Vue2 快约 55%,得益于模块化架构和编译时优化。
  3. 更新速度:比 Vue2 快约 133%,归功于虚拟 DOM 优化和响应式系统升级。
  4. 内存占用:比 Vue2 减少约 54%,减少了不必要的依赖追踪。

第七,typescript的支持。

  • Vue2:对 TypeScript 支持较弱,需通过 

vue-class-component 等库增强,类型推断不友好。

  • Vue3:原生支持 TypeScript,源码用 TypeScript 重写,Composition API 基于函数和变量,类型推断更自然,开发体验大幅提升。

好啦,本期文章就到这里,如果大家觉得有需要补充的也可以在评论区提出来,我们下期再见~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-09-28,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 1. 第一阶段:解析(Parsing)—— 将模板转化为抽象语法树(AST)
    • (1)词法分析(Lexical Analysis):拆分 “模板 token”
    • (2)语法分析(Syntactic Analysis):构建 AST
  • 2. 第二阶段:优化(Optimization)—— 提升执行效率
  • 3. 第三阶段:生成(Code Generation)—— 将 AST 转化为可执行代码
  • 一、setup 函数:Composition API 的入口
    • 1. 执行时机
    • 2. 参数
    • 1. ref:处理基本类型与引用类型
    • 2. reactive:处理对象类型
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档