大家好,我是作曲家种太阳,本次的专栏会带你一步步实现一个mini-vue3,每个小节都都回有一些测试,验证当前的一个逻辑,并且我已经把代码上传到github上了,可以根据每个章节去看对应的源码提交记录。
本章介绍循序渐进的介绍vue3的响应式系统,没有需要写的代码,预计30分钟看完
🌟 一、响应系统的概念与意义
响应系统的本质:
数据变化,视图自动更新。
🌟 二、为什么需要响应式(JS 的程序性)
默认情况下 JavaScript 的程序性特征:
- 程序执行流程固定,不会自动随数据变化更新结果。
比如:
js
let price = 10
let quantity = 2
let total = price * quantity
console.log('总价格:', total) // 总价格:20
quantity = 5
console.log('总价格:', total) // 总价格:20 (❌期望是50)
此时发现:
- 当依赖的数据(quantity)变化时,计算结果(total)不会自动更新,违背了我们的期望。
🌟 三、如何实现响应式(effect思想)
最初的思想:
- 封装计算逻辑到一个函数里(effect),数据变化时重新执行它。
js
let total
const effect = () => {
total = price * quantity
}
effect()
console.log(total) // 20
quantity = 5
effect() // 重新执行
console.log(total) // 50(✅ 符合期望)
但存在明显缺点:必须主动调用 effect。
🌟 四、Vue2 如何实现响应式(Object.defineProperty)
核心 API:
Object.defineProperty
:监听对象中已有的指定属性的 getter 和 setter。
使用示例:
js
const product = { price: 10, quantity: 2 }
let total = 0
Object.defineProperty(product, 'quantity', {
set(newVal) {
quantity = newVal
total = product.price * quantity // 自动计算
},
get() {
return quantity
}
})
特点:
- 自动响应变化,无需显式调用 effect。
🌟 五、Vue2 响应式设计的缺陷
Vue2 官方文档:
- 由于 JavaScript 限制,Vue 无法监听对象属性新增、删除,也不能直接监听数组元素变化。
原因:
Object.defineProperty
只能监听已存在的属性。- 通过数组下标新增元素,或对象新增属性,无法监听到变化。
例子:
js
// 对象
product.newProp = '新增属性' // 不响应
// 数组
arr[5] = '新增元素' // 不响应
Vue2 提供 Vue.set()
方法解决,但不够优雅。
🌟 六、Vue3 响应式的核心:Proxy
为了克服 Vue2 的不足,Vue3 使用新的响应式核心 API ------ Proxy:
Proxy 本质:
- 代理对象上的所有操作(新增、删除、读取、修改属性)都能被捕获。
示例代码:
js
复制编辑
const product = { price: 10, quantity: 2 }
const proxyProduct = new Proxy(product, {
get(target, key) {
// 收集依赖(getter)
return Reflect.get(target, key)
},
set(target, key, val) {
// 触发更新(setter)
return Reflect.set(target, key, val)
}
})
proxyProduct.newProp = '响应式新增属性' // ✅响应式!
优势:
- 不再存在新增属性无法响应的问题。
🌟 七、Proxy 和 Reflect 为什么搭配使用?
Reflect
的作用:
- 与 Proxy 配合,对对象操作提供标准化的、更加安全可靠的方式。
Reflect 的常见方法:
Reflect.get(target, key, receiver)
Reflect.set(target, key, value, receiver)
Reflect.has(target, key)
Reflect.deleteProperty(target, key)
为何需要 Reflect?
- 如果直接使用
target[key]
,getter 中的this
指向原始对象而非 Proxy,这会导致响应性丢失。 - 使用 Reflect 可以明确将 getter 和 setter 操作代理到 Proxy 身上,确保所有访问都经过响应式逻辑处理。
示例说明 Reflect 的必要性:
js
复制编辑
const p1 = {
first: '张',
last: '三',
get fullName() {
return this.first + this.last
}
}
const proxy = new Proxy(p1, {
get(target, key, receiver) {
console.log('getter被触发')
return Reflect.get(target, key, receiver) // receiver 为 proxy 实例
}
})
console.log(proxy.fullName)
// getter被触发(fullName)
// getter被触发(first)
// getter被触发(last)
- 如果不用 Reflect,只有第一次被触发,内部的属性调用不再触发 getter。
🌟 八、Vue 响应系统总结(Vue2 与 Vue3 对比)
特点 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy + Reflect) |
---|---|---|
对象已有属性响应性 | ✅ 可以监听 | ✅ 可以监听 |
新增属性的响应性 | ❌ 无法监听(需用 Vue.set) | ✅ 自动响应 |
数组元素新增 | ❌ 无法直接响应(需用Vue.set) | ✅ 自动响应 |
删除属性 | ❌ 无法监听 | ✅ 可以监听 |
性能与实现复杂度 | 中等(需额外API) | 更优(统一API) |
IE兼容性 | ✅ 支持 IE9+ | ❌ Proxy 不支持 IE11 |
为什么 Vue3 放弃
Object.defineProperty
改用 Proxy?
因为 Proxy 能代理整个对象的所有操作,解决了 Vue2 中无法监听新增或删除属性的问题,同时配合 Reflect 能保证所有 getter/setter 的内部调用都通过 Proxy,避免响应性丢失。