MVVM模型

MVVM 模型其实就是一种让代码分工更明确的开发模式,主要分三个部分:
Model(数据模型),对应data() 返回的对象 + Pinia/Vuex状态。负责存数据,和处理数据逻辑。比如从数据库读写、网络请求这些
View(视图层),对应模板语法(<template> 中的 HTML),只负责显示数据和接收用户操作。不处理数据逻辑;
ViewModel(视图模型),是Vue 实例本身(处理数据绑定/事件监听),一个中间层,相当于数据和界面之间的桥梁

双向绑定原理(ViewModel 的核心)

  1. 模板编译:将模板解析为 Render 函数
  2. 依赖收集:渲染时触发 getter 记录依赖关系
  3. 派发更新:数据变更时通知关联组件更新
    vue实例对象前端主流的框架都是这个思想:把数据放在要求的位置(Model),写出模板代码(view)。里面具体怎么插入值中间的vm实例对象来操作

最核心的优点:

前端程序员可以专注写界面,后端程序员专注处理数据,两边互不干扰
界面变化不会影响数据逻辑,数据逻辑改了也不用动界面代码
并且方便写单元测试,因为ViewModel可以独立测试

Vue2的数据代理

核心目标:让我们在组件中能用this.xxx直接访问/修改 data 中的属性,而不是繁琐的this._data.xxx
Object.defineProperty是JavaScript提供的一个底层API,它允许精确地定义或修改对象上的属性,特别是能定义getter/setter实现访问器属性。Vue2的核心响应式系统正是建立在这个API之上,它通过两步走:

  1. 首先创建 _data 存储原始数据
    Vue会执行 data() 函数,得到原始数据对象,直接保存到vm._data属性上
  2. 第二步进行数据劫持 (Observer):使用Object.defineProperty递归地将保存在 vm._data 上的属性转换成 getter/setter,用于实现响应式。
    (在getter中收集依赖(记录谁在用这个数据),在setter中通知更新(告诉依赖者数据变了)。)
  3. 第三步进行数据代理:同样使用 Object.defineProperty,在vm上为_data的每个顶层属性,创建一层代理。当我们访问或修改 vm.xxx 时,实际上是通过代理的 getter/setter 去访问或修改 vm._data.xxx
    这层代理让我们能直接用 this. 操作数据,语法更简洁自然。

局限

无法检测对象属性的新增 / 删除;无法监听数组索引和长度变化

Object.defineProperty

Object.defineProperty是JavaScript 提供的一个底层API,它允许精确地定义或修改对象上的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
let person={
name:'枫糖',
sex:'女',
}
Object.defineProperty(对象名,'属性名',{
})
Object.defineProperty(person,'home',{
value:'泽兰'
})
console.log(person)//{name:'枫糖',sex:'女',home:'泽兰'}
for (let key in person) {
console.log(key); // 只输出 'name', 'sex'
}
  • Object.defineProperty 添加的属性默认不可枚举,默认不会出现在 for…in 循环或 Object.keys() 的结果里
    解决:显式设置 enumerable: true
  • Object.defineProperty 添加的属性值默认不可以修改
    解决:writabe:true
  • Object.defineProperty 添加的属性不可以随意删除
    解决: 设置configurable: true
  1. get和set访问器属性
    xxx属性本身并不存储值。它的值是通过get函数动态返回的(这里是 _xxx 的值)。当给它赋值时,是通过set函数去修改关联的_xxx变量

实现步骤

  1. 存储原始数据 (_data):Vue 在创建组件实例时,会执行我们定义的 data() 函数,得到原始数据对象,并将它直接保存到实例的 _data 属性上。此时,原始数据在 vm._data上
  2. 代理到实例 (vm):Vue会遍历data对象的所有顶层属性,对于每一个属性key,使用Object.defineProperty在组件实例本身上定义一个同名属性
  3. 定义代理的 get/set
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Object.defineProperty(vm, key, {
    get() {
    return vm._data[key]; //访问 this.key 实际上是访问 this._data.key
    },
    set(newValue) {
    vm._data[key] = newValue; //修改 this.key 实际上是修改 this._data.key
    },
    enumerable: true,
    configurable: true
    });
    当写this.name或 {{ name }} 时:
    读取 (get):触发定义在 vm 上的 name 属性的 getter,它返回 vm._data.name 的值。
    修改 (set):触发定义在 vm 上的 name 属性的 setter,它将新值赋给 vm._data.name

_data?

_data是Vue内部存储原始响应式数据的地方

vue3的数据代理和数据劫持

代理与劫持合一:reactive()返回的Proxy对象同时实现了对整个对象的代理和劫持
无中间层:直接操作代理对象(无 _data 概念)
精准更新:通过 track/trigger 实现属性级依赖追踪

解决:

解决历史痛点: 完美支持了数组索引/长度修改和对象属性的动态增删,开发者可以直接使用原生 JS 语法操作数组和对象,彻底告别 Vue.set 和 Vue.delete。这极大简化了开发心智模型。

性能与精度提升: 通过 惰性转换 减少初始化开销,并通过 Proxy 的能力实现了 更细粒度的依赖追踪,只在真正访问到的属性变化时才触发更新,优化了运行效率。

ref 的定位: ref 主要用于处理基本类型值和需要保持稳定引用的场景,通过 .value 访问其值。在模板中会自动解包。reactive 则专注于处理对象和数组。

核心思想:Proxy取代 Object.defineProperty

Vue3的响应式系统永强大的原生 JavaScript 特性:Proxy 和 Reflect

  1. Proxy是什么
    它允许你创建一个对象的代理(Proxy)。这个代理可以拦截并重新定义对该对象的基本操作(如属性读取、设置、删除、方法调用等)。

它代理的是整个对象,而不是像 Object.defineProperty 那样需要递归遍历并逐个定义对象的属性。

拦截操作范围更广: 可以拦截 get, set, deleteProperty, has, ownKeys 等十多种操作。这是实现更完善响应式的关键。
2. Vue 3 如何利用 Proxy 实现响应式?
Vue 3 主要通过两个核心函数:reactive() 和 ref()。
reactive(obj): 这是 Vue 3 处理对象 (Object) 和数组 (Array) 响应式的主力。

1
2
3
4
5
6
import { reactive } from 'vue';
const state = reactive({
count: 0,
user: { name: 'Alice' },
hobbies: ['reading', 'coding']
});

内部过程: reactive 函数接收一个普通对象,然后返回这个对象的 Proxy 代理。

劫持与代理合一: 这个 Proxy 同时完成了 Vue 2 中 数据代理(方便访问) 和 数据劫持(追踪依赖、触发更新) 的工作

解决Vue2的痛点

  1. 完美支持数组索引和变更方法:

    Vue 2 问题: 无法检测通过索引直接设置项 (arr[index] = newValue) 或修改数组长度 (arr.length = 0)。需要特殊处理数组的 push, pop, shift, unshift, splice, sort, reverse 方法。
    Vue 3 解决: Proxy 的 set 陷阱可以完美拦截 arr[index] = newValue 和 arr.length = newLength 操作。数组的内置方法(如 push)内部会执行索引设置或 length 修改,自然会被 Proxy 捕获。不再需要特殊 hack 数组方法!

  2. 完美支持对象属性的动态添加和删除:
  3. 更细粒度的依赖追踪:
    Vue2 追踪的依赖是对象属性的 getter。嵌套对象需要递归遍历,初始化开销大
  4. 更统一简洁的实现:
    Vue 2: 需要区分数据代理 (vm 属性代理 _data) 和数据劫持 (Observer 处理 _data)。实现相对复杂。
    Vue 3: reactive() 返回的 Proxy 代理一步到位

Options API

Vue的OptionsAPI,我的理解是:它是 Vue2的默认组件组织方式,也是 Vue 3 完全兼容的经典模式。它以声明式的“选项”对象为核心,将组件的不同功能逻辑,划分到预定义好的“格子”里,核心思想是按功能类型划分代码

痛点:逻辑分散:同一功能的代码分散在 data/methods/computed/watch中,复用困难:混入(mixins)导致命名冲突和隐式依赖

核心思想:按功能类型划分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 典型的 Options API 组件
export default {
// 1. 组件标识
name: 'MyComponent',

// 2. 模板依赖
components: { ChildComponent },
directives: { focus },

// 3. 外部接口
props: { message: String },
emits: ['submit'],

// 4. 本地响应式状态
data() {
return {
count: 0,
user: { name: 'Alice' }
};
},

// 5. 计算属性 (基于状态的衍生值)
computed: {
doubledCount() {
return this.count * 2;
},
greeting() {
return `Hello, ${this.user.name}!`;
}
},

// 6. 方法 (事件处理、业务逻辑)
methods: {
increment() {
this.count++;
},
handleSubmit() {
this.$emit('submit', this.user);
}
},

// 7. 生命周期钩子 (组件各个阶段执行的操作)
created() {
console.log('Component created!');
},
mounted() {
console.log('Component mounted to DOM!');
this.fetchData();
},
beforeUnmount() {
console.log('Component about to be destroyed!');
this.cleanup();
},

// 8. 侦听器 (响应状态变化执行异步或复杂操作)
watch: {
count(newVal, oldVal) {
console.log(`Count changed from ${oldVal} to ${newVal}`);
},
'user.name'(newName) {
console.log(`User's name changed to ${newName}`);
}
}
}

data():定义组件的本地响应式状态

为什么必须是函数:组件会被复用,如果 data 是对象,所有实例共享同一份数据(引用类型浅拷贝问题)
Vue遍历 data 对象的所有属性,vue2中用Object.defineProperty 添加 getter/setter

props用来声明组件接收的外部传入的属性

computed计算属性:定义基于响应式状态的衍生值

计算结果会被缓存,只有依赖的响应式状态变化时才会重新计算。

声明: 函数形式 (computed: { fullName() { return this.firstName + ‘ ‘ + this.lastName; } })。
使用: 像普通属性一样使用 ({{ fullName }} 或 this.fullName)

methods方法:定义可以在模板,或组件逻辑中调用的函数。用于事件处理、业务逻辑封装。

声明: 普通函数 (methods: { handleClick() { … } })。
this 绑定: Vue 自动将 methods 中的函数绑定到组件实例 (this),避免丢失上下文。
注意: 不要使用箭头函数定义方法,否则this不会指向组件实例。
箭头函数没有自己的this,会继承父级作用域的this,指向window无法被Vue重重新绑定

watch:在响应式状态发生变化时执行

默认只监听引用变化,对象内部属性变化不会触发

可以侦听 data/props/computed 属性。支持深度侦听 (deep: true)、立即执行 (immediate: true)、回调接收新旧值。
生命周期钩子 (如 created, mounted, updated, beforeUnmount 等)

Composition API

将同一功能的代码放在一个setup()函数中,同一功能的状态/方法/生命周期可以集中书写
灵活复用:通过自定义hook复用逻辑替代 Mixins(解决命名冲突)
类型支持:更好的 TypeScript 集成(setup 函数返回明确类型

Vue 2和Vue3之间的不同

Vue 3 是全面进化的版本

  1. 响应式系统
    Vue 2: 基于Object.defineProperty实现响应,需递归遍历+单独处理数组式数据绑定
    Vue 3: 用Proxy实现对象级监听
    vue3的核心技术是Proxy + Reflect,代理整个对象,无需递归初始化属性
    vue3可以直接进行数组索引修改;动态增删属性
  2. API设计
    Vue 2: 主要采用Options API,按功能类型分块,比如(如data, methods, computed等)选项
    Vue 3: Composition API, 允许将同一功能的状态/方法/生命周期集中书写,解决逻辑复用
  3. 生命周期钩子
    Vue 2: 提供了一系列生命周期钩子,比如beforeCreate, created, beforeMount, mounted等。
    Vue 3: 精简并重组了这些钩子,例如beforeDestroy变成了onBeforeUnmount,destroyed变为onUnmounted,beforeCreate被setup() 取代
  4. 性能优化
    编译时优化:PatchFlag标记动态节点,Diff效率提升
    虚拟DOM优化、按需编译、静态提升、事件缓存、Proxy 响应式系统、Tree-shaking 等

    按需编译:Vue3 的编译器会分析模板,只生成需要的代码(如静态节点不再重复渲染)
    静态内容(如纯文本)只创建一次,不再参与 diff 计算
    内联事件处理函数会被缓存,避免每次渲染都创建新函数
    打包体积:Vue2全量引入,Vue3Tree-shaking 按需引入

  5. 生态系统
    Vue3 推荐使用Vite 构建工具;Pinia状态管理库,对TypeScript支持更好,API更简洁;VueUse,基于 Composition API 的工具库提供大量可复用的hook

生命周期

Vue3 的生命周期是指组件从创建到销毁过程中的一系列阶段,每个阶段都有对应的生命周期钩子函数,让开发者可以在特定阶段执行相应的逻辑

组件创建阶段

setup:在组件创建之前执行,用于初始化组件的响应式数据、计算属性、方法等,是 Composition API 的入口点。比如在setup中可以定义组件的初始状态数据const count = ref(0)。
onBeforeMount:在组件挂载到 DOM 之前调用。此时组件的render函数已经被调用,但还没有实际的 DOM 节点被创建。可以在这个钩子中进行一些数据的最后准备工作,比如对即将渲染的数据进行最后的格式化。
onMounted:组件挂载到 DOM 后调用。此时可以访问到真实的 DOM 节点,常用于发送网络请求获取数据,然后更新组件状态来渲染数据到页面上,或者进行一些需要 DOM 节点的初始化操作,如echarts图表的初始化。

组件更新阶段

onBeforeUpdate:在组件更新之前调用,此时组件的响应式数据已经发生了变化,但 DOM 还没有更新。可以在这个钩子中做一些数据更新前的准备工作,比如保存旧的数据状态。
onUpdated:组件更新完成后调用,此时 DOM 已经根据最新的数据进行了更新。可以在这个钩子中执行一些依赖于更新后 DOM 的操作,比如操作更新后的 DOM 元素,或者根据新的 DOM 状态重新计算一些布局相关的值。

组件销毁阶段

onBeforeUnmount:在组件卸载之前调用。可以在这个钩子中进行一些清理工作,比如清除定时器、取消网络请求、解绑全局事件等,以避免内存泄漏。
onUnmounted:组件卸载后调用。此时组件实例以及相关的 DOM 都已经被销毁,组件相关的所有资源都应该被清理干净。

其他钩子

onErrorCaptured:当组件或其子组件发生错误时会被调用。可以用于捕获错误并进行统一的错误处理,比如记录错误信息、展示错误提示给用户等,有助于提高应用的稳定性和可维护性。

Options API 生命周期钩子是预定义的组件选项,与 data、methods 并列。它们是 “被动声明” 的,由 Vue 在特定时刻调用。
Composition API :生命周期钩子是可注册的函数,在setUp中主动调用
Vue 3 setup() 取代了 beforeCreate/created,成为组合式逻辑的入口;
钩子变为可注册的函数 (onXxx),支持在功能逻辑块内就近多次注册
命名更语义化,on开头
解决了Options API 生命周期逻辑分散的痛点,实现功能内聚

生命周期对比

组件通信

父子组件:props + emit(如表单控件)
祖孙层级深:provide/inject(如主题/语言)
全局状态:Pinia(如用户登录状态)
兄弟组件:提升状态到父级 或 用 Pinia
特殊需求:$refs(调用子组件)/$attrs(透传) → Vue 3合并$attrs
Vue 3 黄金法则:能用 provide 不用事件总线,能用 Pinia 不用 Vuex

父子通信

父传子:Props

父组件传递数据:在父组件的模板中,通过在子组件标签上以属性名=“属性值”的形式来传递数据。如<Child :msg="parentMsg" />,这里parentMsg是父组件中定义的响应式数据,:是 Vue 的指令语法,用于将parentMsg的值绑定到子组件的msg属性上。
子组件接收数据:在子组件中,使用defineProps宏来声明接收父组件传递的Props。
如const props = defineProps(['msg']);,这样就可以在子组件中通过props.msg来访问父组件传递过来的数据了。

子传父:自定义事件

子组件触发事件:子组件通过defineEmits宏来定义可以触发的自定义事件。如const emit = defineEmits([‘handle - event’]);,然后在子组件的方法中使用emit函数来触发事件,如**emit(‘handle - event’, ‘子组件的数据’);**,这里第一个参数是事件名,第二个参数是要传递给父组件的数据。
父组件监听事件:在父组件的子组件标签上使用@事件名=“事件处理函数”的形式来监听子组件触发的事件。如<Child @handle - event="handleChildEvent" />,handleChildEvent是父组件中定义的方法,当子组件触发handle - event事件时,父组件的handleChildEvent方法就会被调用,并且可以接收到子组件传递过来的数据。

子组件接收数据:
通过 defineProps 声明 Props,支持两种方式:
数组形式:defineProps([‘msg’, ‘count’, ‘user’])(适用于简单类型)。
对象形式:可指定类型、必填项、默认值(如 msg: { type: String, required: true }),用于类型校验和默认值设置。

代码例子

  1. 父子组件通信:
    事件上传:Vue 2this.$emit('event', data),vue3defineEmits + emit('event')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    <template>
    <!-- 1. 在子组件标签上用 `:` 绑定数据(父传子) -->
    <Child
    :msg="parentMsg" <!-- 传递字符串数据 -->
    :count="counter" <!-- 传递响应式数值 -->
    :user="currentUser" <!-- 传递对象数据 -->
    @child-event="handleChildEvent" <!-- 监听子组件事件 -->
    />

    <!-- 父组件自身内容 -->
    <div>
    <p>父组件计数器:{{ counter }}</p>
    <button @click="incrementCounter">+1 父组件计数器</button>
    </div>
    </template>

    <script setup>
    // 引入 Vue 响应式工具
    import { ref, reactive } from 'vue';

    // 1. 定义父组件数据
    const parentMsg = '这是父组件传递的字符串'; // 普通字符串
    const counter = ref(0); // 响应式数值(修改后视图自动更新)
    const currentUser = reactive({ // 响应式对象
    name: 'Alice',
    age: 25
    });

    // 2. 定义处理子组件事件的函数
    const handleChildEvent = (data) => {
    console.log('子组件传递的数据:', data); // 控制台打印子组件数据
    // 父组件可根据子组件数据做逻辑处理(示例:更新计数器)
    counter.value += 1;
    };

    // 3. 父组件自有方法(示例:修改计数器)
    const incrementCounter = () => {
    counter.value += 1;
    };
    </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<template>
<!-- 子组件接收并展示父组件数据 -->
<div class="child-component">
<h3>子组件接收到的 Props:</h3>

<!-- 1. 展示字符串 Props -->
<p>字符串数据:{{ msg }}</p>

<!-- 2. 展示数值 Props(响应式,父组件修改后自动更新) -->
<p>计数器:{{ count }}</p>

<!-- 3. 展示对象 Props -->
<p>用户信息:{{ user.name }},年龄 {{ user.age }}</p>

<!-- 子组件触发事件(子传父) -->
<button @click="sendDataToParent">
点击传递数据给父组件
</button>
</div>
</template>

<script setup>
// 1. 声明接收父组件的 Props
const props = defineProps({
// 方式1:简单数组声明(适用于非复杂类型)
msg: { type: String, required: true }, // 字符串类型,必填
count: { type: Number, default: 0 }, // 数值类型,默认值 0
// 方式2:对象声明(适用于复杂类型校验)
user: {
type: Object,
required: true,
default: () => ({}) // 对象默认值需返回函数
}
});

// 2. 定义触发父组件事件的函数
const emit = defineEmits(['child-event']); // 声明自定义事件

const sendDataToParent = () => {
// 触发事件并传递数据(子传父)
emit('child-event', {
timestamp: new Date().toLocaleString(), // 时间戳
message: '来自子组件的消息'
});
};
</script>
  1. 跨层级通信Provide/Inject
    Vue 2:选项式:provide: { key: val } inject: [‘key’]
    Vue 3:函数式:provide(key, value) inject(key)
    事件总线:
    Vue2: new Vue() 作为 EventBus
    Vue3:废弃EventBus → 改用 mitt库
    1
    2
    3
    4
    5
    6
    // Vue 3 Provide 响应式方案
    import { provide, ref } from 'vue'
    setup() {
    const count = ref(0)
    provide('count', count) // 子孙组件注入的 count 是响应式的
    }
  2. 全局状态管理
    1
    2
    3
    4
    5
    6
    7
    8
    export const useStore = defineStore('main', {
    state: () => ({ count: 0 }),
    actions: {
    increment() {
    this.count++ // 直接修改(无需 mutations)
    }
    }
    })

虚拟 DOM与Diff 算法

为什么用虚拟DOM:性能开销大,代码维护难
理解:真实DOM结构复杂,每次修改都会触发浏览器的重排和重绘,频繁直接操作 DOM 会导致代码混乱,难以维护。
目的:高效地更新DOM
对于简单场景,直接操作DOM可能更快。虚拟 DOM 的优势在于复杂视图频繁更新时的性能优化。
虚拟DOM的核心流程分为三步:生成 → 对比(diff) → 更新

虚拟DOM(主流框架的默认选择)

虚拟DOM是一种用JavaScript对象来模拟真实DOM树的技术。它是对真实 DOM 的抽象,以更高效的方式来更新和操作DOM,是一种轻量级的 JavaScript 对象
每个虚拟DOM节点对应一个真实DOM节点,但不包含真实DOM的所有属性(如事件监听、样式计算等)
一个虚拟DOM节点通常包含:标签名,属性,子节点
虚拟DOM节点的基本结构

1
2
3
4
5
6
7
8
9
10
11
12
{
type: 'div', // 节点类型(标签名或组件)
props: { // 节点属性
className: 'container',
onClick: () => console.log('点击')
},
children: [ // 子节点列表
{ type: 'h1', children: ['标题'] },
{ type: 'p', children: ['内容'] }
],
dom: null // 对应真实DOM节点的引用(可选)
}

虚拟DOM一定比直接操作DOM快吗

性能对比,三个角度考虑:

  1. 单次更新场景,真实DOM好
    真实 DOM:直接操作(如dom.innerHTML = ‘新内容’),性能开销极低
    虚拟 DOM:需经历「生成虚拟节点→Diff 对比→更新真实 DOM」流程,存在额外计算开销
  2. 频繁更新场景,虚拟DOM好
    真实 DOM:每次更新都触发浏览器重排重绘,若更新频率极高(如每秒 100 次),可能导致卡顿
    虚拟 DOM:通过批量更新和 Diff 算法,将多次更新合并为一次真实 DOM 操作,减少浏览器渲染压力
  3. 看节点复杂度
    若唯一节点包含复杂事件绑定或嵌套结构,虚拟 DOM 的抽象层仍能提供统一的更新逻辑;若只是简单文本节点,真实 DOM 更轻量。
    虚拟DOM应用场景:数据频繁更新的应用(如实时图表、聊天界面),复杂 UI 交互(如拖拽、动态表单),跨平台开发(React Native、Vue Weex)

开发与维护成本:

  1. 开发效率
    真实DOM:直接操作API,代码更直观,但需手动处理所有细节(如事件绑定、属性更新)。
    虚拟DOM:依赖框架(如 React/Vue),通过声明式编程描述 UI,代码更简洁(如{ count: 10 }自动映射为 DOM 内容)
  2. 维护成本
    真实 DOM:当逻辑复杂时(如条件渲染、动画交互),代码易变得碎片化,难以维护。
    虚拟 DOM:框架提供的组件化机制可将逻辑封装,便于复用和调试(如 React 的组件生命周期、Vue 的响应式系统)

diff算法

找出新旧虚拟 DOM 树的差异,最小化真实 DOM 操作
静态提升:纯静态节点只创建一次,不再参与 diff
需要用到Key属性,key用于标识列表中的节点,帮助 Diff 算法快速识别哪些元素被添加、删除或移动

现代 Diff 算法的优化策略

分层比较:只比较同一层级的节点,不跨层级比较。
(如果发现节点不存在,直接删除整个子树,不会继续比较子节点)
相同类型节点:
如果节点类型相同(如都是div),则只更新属性和内容,保留子节点继续比较。
列表比较的 Key:
为列表项提供唯一的key,帮助算法识别哪些元素被添加、删除或移动。
组件比较:
同一类型的组件,保留实例并更新状态;不同类型的组件,直接替换。

计算属性和监听器

计算属性

它的值是根据其他响应式数据计算出来的。它会自动追踪依赖的响应式数据,只有这些依赖数据变化时,才会重新计算。而且计算属性是有缓存的,如果依赖的数据没有变化,多次访问计算属性会直接返回缓存的值,而不会重新计算

监听器

专门负责观察某个或某些响应式数据的变化。一旦被监听的数据发生了改变,就会触发相应的回调函数,你可以在回调函数中执行任何你需要的操作,比如发送网络请求、更新其他相关数据、记录日志等。
比如:假设要根据用户输入的搜索关键词来进行实时搜索,并在控制台打印出搜索结果。

如何实现 Vue Router 的懒加载?

可以通过动态导入的方式实现懒加载
在路由配置中写成component: () => import('../views/Home.vue')
这样做的好处是按需加载,提升首屏加载速度,避免一开始就加载全部组件

什么是组件化开发?为什么要用组件化

组件化开发就是把页面拆分成一个个独立、可复用的小模块,每个模块负责自己的功能。
这样做可以让代码结构更清晰,提高可维护性,还能提高开发效率,因为组件可以重复使用。

其他高频问题(简答版)

Q1:如何在 Vue3 中使用 ref 和 reactive?它们的区别是什么?
ref 用于创建一个响应式的基本类型或对象引用。
reactive 用于创建一个响应式的对象。

1
2
3
4
5
6
7
8
9
import { ref, reactive } from 'vue';

const count = ref(0);
const state = reactive({ count: 0 });

// 区别在于 `ref` 返回一个对象,值存储在 `.value` 属性中
console.log(count.value); // 0
console.log(state.count); // 0

Q2:<script setup>是什么
<script setup> 是一种编译时语法糖,可以在单文件组件(SFC)中更简洁地使用 Composition API。
所有在