WinddSnow

Vue-3-Reactivity-System-Proxy-Dependency-Tracking

字数统计: 5.5k阅读时长: 22 min
2026/08/01

第103课:Vue 3 响应式原理——reactiveref、Proxy、依赖收集与触发

Vue 3 最核心的变革之一是响应式系统从 Object.defineProperty 迁移到了 Proxy。这不仅仅是底层 API 的替换——它彻底解决了 Vue 2 响应式系统的两大局限:无法检测对象属性的添加和删除,以及无法直接拦截数组的索引修改和 length 变更。Vue 3 的响应式系统通过 Proxy 实现了对任意嵌套对象的完整代理,结合 Reflect 进行默认行为转发,并通过依赖收集(track)派发更新(trigger) 构建了一套精确、高效的订阅-发布机制。理解这套机制的原理,是掌握 Vue 3 Composition API、自定义响应式逻辑以及 Pinia 状态管理底层的基础。本节课将自底向上,从手动实现一个迷你响应式系统开始,逐步剖析 Vue 3 源码中 reactiverefcomputedwatch 的实现原理。


1. Vue 2 响应式的局限与 Proxy 的解决

1.1 Object.defineProperty 的局限

Vue 2 通过 Object.defineProperty 递归地将对象的每个属性转换为 getter/setter。在 getter 中收集依赖(谁读取了这个属性),在 setter 中触发更新(重新渲染组件)。这种“属性级拦截”存在三个著名缺陷:

  • 无法检测属性的添加和删除:由于 getter/setter 在对象初始化时一次性绑定,后续添加的新属性没有 getter/setter,因此 Vue 2 无法追踪它们。Vue.set(target, key, value)Vue.delete(target, key) 这两个全局 API 正是为了弥补这一缺陷而生。
  • 无法直接拦截数组的索引赋值和 length 修改arr[0] = newValuearr.length = 0 不会触发 setter。Vue 2 通过重写数组的 7 个变异方法(pushpopshiftunshiftsplicesortreverse)来间接实现响应式,但直接通过索引修改仍无效。
  • 性能开销:初始化时需要递归遍历对象的所有嵌套属性,对深层大对象初始化缓慢。

1.2 Proxy 如何解决这些问题

Proxy 是 ES6 引入的元编程特性。它创建一个对象的代理,可以拦截对该对象的13 种基本操作——包括属性读取(get)、属性设置(set)、属性删除(deleteProperty)、属性枚举(ownKeys)等。由于 Proxy 是对象级的拦截(而非属性级),它天然解决了 Vue 2 的三大痛点:

  • 拦截属性添加和删除setdeleteProperty 陷阱可捕获所有属性变更,无论该属性是否预先存在。
  • 拦截数组的索引修改和 length 变更:数组也是对象,Proxy 的 set 陷阱对数组索引同样有效。
  • 惰性代理:只有在访问嵌套对象时才递归地创建其代理,而非初始化时一次性递归。

结合 Reflect API——它提供了与 Proxy 陷阱一一对应的默认行为方法,并正确传递 receiver(确保 getter/setter 中的 this 指向代理对象)——Vue 3 构建了一个比 Vue 2 更强大、更简洁、更高效的响应式系统。


2. 手写一个迷你响应式系统

理解原理的最佳方式是亲手实现。下面我们将自底向上构建一个支持 reactiveeffect 和依赖自动收集/派发的迷你响应式引擎。完整代码约 80 行,但涵盖了 Vue 3 响应式系统的核心骨架。

2.1 目标效果

我们希望实现以下 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
const state = reactive({ count: 0, message: 'hello' });

effect(() => {
console.log('count 更新为:', state.count);
});

effect(() => {
console.log('message 更新为:', state.message);
});

state.count = 1; // 控制台输出: count 更新为: 1
state.message = 'world'; // 控制台输出: message 更新为: world
state.count = 1; // 值未变化,不触发

2.2 第一步:reactive——用 Proxy 创建响应式对象

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
// 存储原始对象到代理对象的映射(用于避免重复代理)
const reactiveMap = new WeakMap();

function reactive(target) {
// 只代理对象类型,基本类型直接返回(或通过 ref 处理)
if (typeof target !== 'object' || target === null) {
return target;
}

// 如果已经代理过,直接返回缓存的代理
if (reactiveMap.has(target)) {
return reactiveMap.get(target);
}

const proxy = new Proxy(target, {
get(target, key, receiver) {
// 收集依赖:谁读取了这个属性
track(target, key);
const result = Reflect.get(target, key, receiver);

// 惰性深度代理:如果属性值是对象,递归代理
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
},

set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);

// 只有当值确实变化时才触发更新
if (oldValue !== value) {
trigger(target, key);
}
return result;
},

deleteProperty(target, key) {
const hadKey = Object.prototype.hasOwnProperty.call(target, key);
const result = Reflect.deleteProperty(target, key);

// 成功删除已存在的属性时触发更新
if (hadKey && result) {
trigger(target, key);
}
return result;
},
});

reactiveMap.set(target, proxy);
return proxy;
}

关键设计

  • reactiveMap 使用 WeakMap 存储原始对象到代理的映射。WeakMap 对键是弱引用,当原始对象不再被其他代码引用时,垃圾回收器可以自动回收该映射条目,防止内存泄漏。
  • get 陷阱中的惰性深度代理:只有当访问到嵌套对象时才调用 reactive(result) 创建子代理,而不是在初始化时递归遍历所有属性。这极大地提升了包含深层大对象的初始化性能。
  • set 陷阱中使用 Reflect.set 并传入 receiver——如果 target[key] 本身是一个 getter/setter,receiver 确保 this 指向代理对象而非原始对象,从而保证 getter 内部对 this 的访问也能被代理拦截。
  • deleteProperty 陷阱是 Vue 2 无法做到的——它使得 delete state.count 能正常触发响应式更新。

2.3 第二步:tracktrigger——依赖收集与派发更新

使用一个全局的 WeakMap 作为依赖存储桶,结构为:

1
WeakMap<target, Map<key, Set<effectFn>>>
  • WeakMap 的键是原始对象(被代理的目标),值是另一个 Map
  • 内层 Map 的键是属性名key),值是一个 **Set**,存储所有依赖于该属性的 effect 函数。
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
// 全局依赖存储
const bucket = new WeakMap();

// 当前正在执行的 effect 函数
let activeEffect = null;

// 注册副作用函数
function effect(fn) {
activeEffect = fn;
fn(); // 立即执行一次,触发依赖收集
activeEffect = null;
}

// 收集依赖
function track(target, key) {
if (!activeEffect) return; // 没有 activeEffect,说明不是在 effect 内部读取

let depsMap = bucket.get(target);
if (!depsMap) {
bucket.set(target, (depsMap = new Map()));
}

let deps = depsMap.get(key);
if (!deps) {
depsMap.set(key, (deps = new Set()));
}

deps.add(activeEffect); // 将当前 effect 加入该属性的依赖集合
}

// 派发更新
function trigger(target, key) {
const depsMap = bucket.get(target);
if (!depsMap) return;

const deps = depsMap.get(key);
if (deps) {
deps.forEach((effectFn) => effectFn()); // 执行所有依赖该属性的 effect
}
}

工作流程解析

  1. effect(() => { console.log(state.count); }) 被调用时,activeEffect 被设置为该匿名函数,并立即执行一次。
  2. 在执行过程中,读取 state.count 触发了 proxyget 陷阱。
  3. get 陷阱中调用 track(target, 'count')track 检测到 activeEffect 不为 null,将该 effect 函数添加到 bucket[target]['count'] 的 Set 中。
  4. 执行结束后,activeEffect 重置为 null
  5. state.count = 1 时,触发 proxyset 陷阱。
  6. set 陷阱中调用 trigger(target, 'count')triggerbucket 中取出依赖于 count 的所有 effect 函数并逐一执行。

2.4 完整可运行的最小响应式系统

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
65
66
67
68
69
70
71
72
// --- 依赖存储 ---
const bucket = new WeakMap();
let activeEffect = null;

function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}

function track(target, key) {
if (!activeEffect) return;
let depsMap = bucket.get(target);
if (!depsMap) bucket.set(target, (depsMap = new Map()));
let deps = depsMap.get(key);
if (!deps) depsMap.set(key, (deps = new Set()));
deps.add(activeEffect);
}

function trigger(target, key) {
const depsMap = bucket.get(target);
if (!depsMap) return;
const deps = depsMap.get(key);
if (deps) deps.forEach(fn => fn());
}

// --- reactive ---
const reactiveMap = new WeakMap();

function reactive(target) {
if (typeof target !== 'object' || target === null) return target;
if (reactiveMap.has(target)) return reactiveMap.get(target);

const proxy = new Proxy(target, {
get(target, key, receiver) {
track(target, key);
const result = Reflect.get(target, key, receiver);
if (typeof result === 'object' && result !== null) return reactive(result);
return result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) trigger(target, key);
return result;
},
deleteProperty(target, key) {
const hadKey = Object.prototype.hasOwnProperty.call(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey && result) trigger(target, key);
return result;
},
});

reactiveMap.set(target, proxy);
return proxy;
}

// --- 测试 ---
const state = reactive({ count: 0, message: 'hello' });

effect(() => {
console.log('count:', state.count);
});

effect(() => {
console.log('message:', state.message);
});

state.count = 1; // count: 1
state.message = 'world'; // message: world
state.count = 1; // 值未变化,不触发

3. ref:基本类型的响应式包装

reactive 只能代理对象类型。对于基本类型(stringnumberboolean),Vue 3 提供了 ref 函数。ref 内部创建了一个带有 value 属性的对象,并对这个对象使用 reactive(或直接通过 getter/setter)实现响应式。

3.1 简化版 ref 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function ref(rawValue) {
// 创建一个包含 value 属性的对象
const r = {
get value() {
track(r, 'value');
return rawValue;
},
set value(newValue) {
if (rawValue !== newValue) {
rawValue = newValue;
trigger(r, 'value');
}
},
};
return r;
}

使用差异:在 setup<script setup> 中,ref 创建的变量需要通过 .value 访问和修改。但在模板中,Vue 会自动“解包”(unwrap)ref,因此可以直接使用 {{ count }} 而非 {{ count.value }}。这一便利是由模板编译器在编译阶段自动处理的。

3.2 refreactive 的关系

ref 接收一个对象作为参数时,它内部会调用 reactive 来代理该对象:

1
2
3
4
5
6
7
8
9
10
11
12
function ref(rawValue) {
if (typeof rawValue === 'object' && rawValue !== null) {
// 对象类型:内部使用 reactive 代理
return { value: reactive(rawValue) };
}
// 基本类型:使用 getter/setter
const r = {
get value() { track(r, 'value'); return rawValue; },
set value(newVal) { if (rawValue !== newVal) { rawValue = newVal; trigger(r, 'value'); } },
};
return r;
}

选择建议

  • 包装基本类型(数字、字符串、布尔值)使用 ref
  • 包装对象/数组使用 reactive。若使用 ref 包装对象,需要使用 .value 访问属性,稍显冗余(模板中会自动解包,但在 setup 脚本中仍需 .value)。
  • 在 Composition API 中,如果希望将对象的所有属性保持为响应式独立引用(可分别解构且保持响应性),可以使用 reactive 配合 toRefs

4. Vue 3 响应式 API 全景

Vue 3 暴露了以下响应式核心 API,它们都建立在上述原理之上:

API 用途 实现基础
reactive() 创建对象的深层响应式代理。 Proxy
ref() 包装基本类型或对象为响应式引用。 getter/setter 或 reactive
shallowReactive() 仅对对象的第一层属性做响应式代理,深层属性不处理。 Proxy(不递归)
shallowRef() .value 本身是响应式的,内部值不做深层处理。 getter/setter
readonly() 创建对象的只读代理(深度),修改会触发警告。 Proxy
shallowReadonly() 仅第一层只读。 Proxy
toRaw() 返回 reactivereadonly 代理的原始对象。 reactiveMap 查找
markRaw() 标记对象永不转为代理。 添加 __v_skip 标记
toRef() reactive 对象的某个属性创建一个 ref(保持响应式连接)。 getter/setter
toRefs() reactive 对象的所有属性转换为独立的 ref toRef 循环
isReactive() 检查对象是否由 reactive 创建。 检查 __v_isReactive
isRef() 检查值是否是一个 ref 对象。 检查 __v_isRef

5. computed 的原理

computed 创建一个惰性求值的、可缓存的计算属性。它的核心是:仅在依赖的响应式数据发生变化时,才重新计算值。如果依赖未变,多次访问 computed 返回的是缓存值。

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
function computed(getter) {
let cachedValue; // 缓存值
let dirty = true; // 脏标志:为 true 时需要重新计算

// 使用 effect 收集依赖
const effectFn = effect(() => {
const newValue = getter();
if (cachedValue !== newValue) {
cachedValue = newValue;
dirty = false; // 计算完成后,标记为干净
}
}, { lazy: true }); // 不立即执行,仅用于依赖收集

return {
get value() {
if (dirty) {
// 触发 getter 执行,同时收集依赖
cachedValue = getter();
dirty = false;
}
// track 依赖:谁读取了 computed 的 value
track(this, 'value');
return cachedValue;
},
};
}

Vue 3 的 computed 在内部是一个 ComputedRefImpl 类。它维护一个 _dirty 标志,当依赖的响应式数据变化时,trigger 会将 _dirty 设为 true,下次访问 computed.value 时才会重新计算。如果依赖未变,直接返回缓存值。此外,computed 本身也是响应式的——当你在 effect 中读取 computed.value 时,effect 会订阅这个 computed,一旦 computed 的值变化,effect 也会重新执行。


6. watch 的原理

watch 用于监听一个或多个响应式数据源的变化,并执行副作用回调。它基于 effect 实现,但有以下增强:

  • 可指定监听源:可以是 refreactive 对象、getter 函数、或它们的数组。
  • 可获得旧值和新值:回调接收 (newValue, oldValue)
  • **可配置 immediateflush**:immediate 在创建时立即执行回调;flush 控制回调执行时机('pre' 组件渲染前、'post' 渲染后、'sync' 同步)。
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
function watch(source, callback, options = {}) {
let getter;
let oldValue;

// 规范化 source
if (isRef(source)) {
getter = () => source.value;
} else if (isReactive(source)) {
getter = () => source;
// 深度监听:遍历所有属性以收集依赖
options.deep = true;
} else if (typeof source === 'function') {
getter = source;
}

if (options.deep) {
// 如果深层监听,在 getter 中递归遍历对象以收集所有深层依赖
const baseGetter = getter;
getter = () => deepTraverse(baseGetter());
}

const job = () => {
const newValue = getter();
callback(newValue, oldValue);
oldValue = newValue;
};

const effectFn = effect(() => getter(), {
lazy: true,
scheduler: () => {
if (options.flush === 'post') {
// 延迟到 DOM 更新后执行
Promise.resolve().then(job);
} else {
job();
}
},
});

if (options.immediate) {
job();
} else {
oldValue = effectFn(); // 首次运行获取初始值
}
}

function deepTraverse(value, seen = new Set()) {
if (typeof value !== 'object' || value === null || seen.has(value)) return value;
seen.add(value);
for (const key in value) {
deepTraverse(value[key], seen);
}
return value;
}

7. 常见陷阱与最佳实践

7.1 解构 reactive 对象会丢失响应性

1
2
3
4
5
const state = reactive({ count: 0, name: 'Alice' });
const { count, name } = state; // ❌ count 和 name 是基本类型值,不再响应式

// 使用 toRefs 保持连接
const { count, name } = toRefs(state); // ✅ count 和 name 现在是 Ref

7.2 替换整个 reactive 对象会丢失响应性

1
2
3
4
5
6
let state = reactive({ count: 0 });
state = reactive({ count: 1 }); // ❌ 新的 reactive 对象与旧的无关,组件不会重渲染

// 使用 ref 包装对象
const state = ref({ count: 0 });
state.value = { count: 1 }; // ✅ ref 的 .value 被替换,响应性保持

7.3 watchdeep 选项与性能

watch(() => state, callback) 默认是浅层监听。如果 state 是一个 reactive 对象,需要使用 { deep: true } 或直接传递 () => state(Vue 会因对象被 reactive 而自动转为深层)。深层监听会递归遍历所有嵌套属性,对大型深层对象会造成性能开销。在可能的情况下,尽量监听具体的属性watch(() => state.user.name, callback)

7.4 避免在模板中直接使用 reactive 对象的解构

<script setup> 中,如果希望模板能够直接使用解构后的变量而保持响应性,应使用 reactive + toRefs,或直接使用多个 ref。这是 Composition API 最常见的编码模式之一。


课后练习

一、概念自测(选择题 / 填空题)

  1. (单选) Vue 3 的响应式系统基于什么 ES6 特性实现?
    A. Object.defineProperty
    B. Proxy
    C. Symbol
    D. Generator

  2. (单选) 以下哪个操作在 Vue 2 中无法被响应式系统自动检测,但在 Vue 3 中可以?
    A. 修改已存在的属性值。
    B. 通过数组的 push 方法添加元素。
    C. 删除对象的某个属性(delete obj.key)。
    D. 读取对象的嵌套属性。

  3. (填空) 在 Vue 3 中,要将基本类型值转换为响应式引用,应使用 ______ 函数。

  4. (多选) 以下哪些关于 Vue 3 响应式系统的描述是正确的?
    A. reactive 内部使用 WeakMap 缓存已创建的代理,避免重复代理。
    B. ref 创建的对象在 <script setup> 中不需要 .value 访问。
    C. computed 采用惰性求值,仅在依赖变化时才重新计算。
    D. watch 默认开启深层监听(deep: true)。

二、AI 编程任务:编写面向 AI 的提示词

场景:你需要实现一个简化的 Vue 3 风格响应式工具库,包含以下三个函数:

  • reactive(obj):使用 Proxy 创建深层响应式代理,支持 getsetdeleteProperty 陷阱。
  • effect(fn):注册副作用函数,自动收集依赖并在依赖变化时重新执行。
  • computed(fn):创建一个计算属性,惰性求值,仅在依赖变化时重新计算。
    要求实现基础的依赖收集(track)和派发更新(trigger)机制,使用 WeakMap 存储依赖关系。代码需要包含使用示例(创建 reactive 对象,注册 effectcomputed,验证行为)。

任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成完整的 JavaScript 响应式库代码。提示词中需明确指定 Proxy 陷阱的类型、WeakMap 的结构以及 computed 的惰性求值实现。

三、Agent 模式下的提示词示例

你是一个资深前端开发 Agent。请创建一个简化版 Vue 3 响应式系统模块。需要创建文件 src/reactivity.js,导出 reactiveeffectcomputed 三个函数。

  • reactive(target):对 target 使用 Proxyget 调用 trackset 检测新旧值是否不同后调用 triggerdeleteProperty 成功后调用 triggerget 中对嵌套对象调用 reactive 实现惰性深度代理。使用 WeakMap 缓存已代理对象(reactiveMap)。
  • track(target, key):将 activeEffect 存入全局 WeakMap<target, Map<key, Set<effect>>> 结构。
  • trigger(target, key):从存储中取出对应 Set 并遍历执行所有 effect
  • effect(fn):设置 activeEffect = fn,执行 fn(),最后重置 activeEffect = null
  • computed(fn):返回 { value } 对象,内部维护 _dirty 标志和 _value 缓存。使用 effect 监听 fn 内部的依赖变化,变化时标记 _dirty = trueget value() 中若 _dirty 则重新执行 fn 并缓存,同时 trackcomputed'value' 属性。
  • 文件末尾添加测试代码(使用 console.log 验证):创建 reactive 对象,注册 effect 打印变化,创建 computed 属性并验证惰性求值和缓存。
  • 所有函数添加 JSDoc 注释。完成后输出完整文件内容。

四、面试真题与参考答案

题目(字节跳动前端面试题):

请详细解释 Vue 3 响应式系统的实现原理。为什么 Vue 3 弃用了 Object.defineProperty 而转向 ProxyProxy 解决了哪些 Vue 2 无法解决或需要特殊 API 才能解决的问题?请结合依赖收集(track)和派发更新(trigger)的流程,说明当修改一个深层嵌套的对象属性时,响应式系统是如何工作的。

参考答案

Vue 3 响应式系统基于 Proxy 实现。reactive 函数创建一个对象的 Proxy,拦截 getsetdeleteProperty 等操作。在 get 陷阱中调用 track 收集当前活跃的 effect 函数;在 setdeleteProperty 陷阱中调用 trigger 派发更新,重新执行所有依赖该属性的 effect。依赖关系存储在一个 WeakMap<target, Map<key, Set<effectFn>>> 的三层结构中。

弃用 Object.defineProperty 转向 Proxy 的原因:Proxy 是对象级代理,可以拦截属性的添加(set)、删除(deleteProperty),以及数组的索引赋值和 length 修改——这些是 Object.defineProperty 的属性级拦截无法处理的。Vue 2 需要通过 Vue.set/Vue.delete 和重写数组方法等 workaround 来弥补,增加了 API 复杂度和开发者的心智负担。此外,Proxy 支持惰性深度代理——仅在访问嵌套对象时才递归创建代理,避免了初始化时一次性递归遍历的性能开销。

修改深层嵌套对象属性(如 state.a.b.c = 1)的工作流程:首先通过 proxy.a 读取 aget 陷阱发现 a 是对象,返回 reactive(a) 的代理。接着 proxy.a.b 读取 b,同理返回 b 的代理。最终 proxy.a.b.c = 1 触发 b 代理的 set 陷阱,检测到 c 的值变化,调用 trigger(b, 'c')trigger 从全局 WeakMap 中取出依赖于 b.ceffect 集合并执行。每一层的代理独立工作,依赖收集也精确到具体属性,因此修改深层属性只会触发依赖该具体属性的 effect,而非父级对象的所有 effect。这保证了更新粒度的高效性。


课后练习答案

一、概念自测答案

  1. B

    • 解析:Vue 3 响应式系统使用 Proxy 实现。Vue 2 使用 Object.defineProperty
  2. C

    • 解析:Vue 3 通过 ProxydeleteProperty 陷阱可以拦截属性删除,Vue 2 需要 Vue.delete 手动处理。A、B、D 在 Vue 2 中均已被支持。
  3. ref

    • 解析:ref 用于将基本类型值包装为响应式对象(通过 .value 访问)。
  4. A、C

    • 解析:A 正确,reactive 使用 WeakMap 缓存代理避免重复。B 错误,ref<script setup> 中仍需 .value,仅在 <template> 中自动解包。C 正确。D 错误,watch 默认是浅层监听,深度监听需显式设置 { deep: true } 或传递 reactive 对象作为 source。

二、AI 编程任务参考答案(提示词示例)

示例提示词
“请实现一个简化版 Vue 3 响应式系统。要求:

  • reactive(obj):使用 Proxy,拦截 get(调用 track,惰性深度代理)、set(新旧值比较,调用 trigger)、deleteProperty(调用 trigger)。WeakMap 缓存。
  • effect(fn):收集依赖,变化时重新执行。
  • computed(fn):惰性求值,带缓存和脏标志。
  • track/trigger 使用 WeakMap<target, Map<key, Set>> 结构。
  • 文件末尾提供测试代码,验证 count 和 computed 行为。
  • 输出完整 JavaScript 文件。”
CATALOG
  1. 1. 第103课:Vue 3 响应式原理——reactive、ref、Proxy、依赖收集与触发
    1. 1.1. 1. Vue 2 响应式的局限与 Proxy 的解决
      1. 1.1.1. 1.1 Object.defineProperty 的局限
      2. 1.1.2. 1.2 Proxy 如何解决这些问题
    2. 1.2. 2. 手写一个迷你响应式系统
      1. 1.2.1. 2.1 目标效果
      2. 1.2.2. 2.2 第一步:reactive——用 Proxy 创建响应式对象
      3. 1.2.3. 2.3 第二步:track 与 trigger——依赖收集与派发更新
      4. 1.2.4. 2.4 完整可运行的最小响应式系统
    3. 1.3. 3. ref:基本类型的响应式包装
      1. 1.3.1. 3.1 简化版 ref 实现
      2. 1.3.2. 3.2 ref 与 reactive 的关系
    4. 1.4. 4. Vue 3 响应式 API 全景
    5. 1.5. 5. computed 的原理
    6. 1.6. 6. watch 的原理
    7. 1.7. 7. 常见陷阱与最佳实践
      1. 1.7.1. 7.1 解构 reactive 对象会丢失响应性
      2. 1.7.2. 7.2 替换整个 reactive 对象会丢失响应性
      3. 1.7.3. 7.3 watch 的 deep 选项与性能
      4. 1.7.4. 7.4 避免在模板中直接使用 reactive 对象的解构
    8. 1.8. 课后练习
      1. 1.8.1. 一、概念自测(选择题 / 填空题)
      2. 1.8.2. 二、AI 编程任务:编写面向 AI 的提示词
      3. 1.8.3. 三、Agent 模式下的提示词示例
      4. 1.8.4. 四、面试真题与参考答案
    9. 1.9. 课后练习答案
      1. 1.9.1. 一、概念自测答案
      2. 1.9.2. 二、AI 编程任务参考答案(提示词示例)