第二章节 响应式原理介绍-【手摸手带你实现一个vue3】

大家好,我是作曲家种太阳,本次的专栏会带你一步步实现一个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,避免响应性丢失。

相关推荐
云端看世界2 分钟前
ECMAScript 类型转换 下
前端·javascript
云端看世界4 分钟前
ECMAScript 运算符怪谈 下
前端·javascript
云端看世界6 分钟前
ECMAScript 函数对象实例化
前端·javascript
前端爆冲6 分钟前
基于vue和flex实现页面可配置组件顺序
前端·javascript·vue.js
云端看世界8 分钟前
ECMAScript 中的特异对象
前端·javascript
il10 分钟前
Deepdive into Tanstack Query - 2.1 QueryClient 基础
前端
_十六12 分钟前
看完就懂!用最简单的方式带你了解 TypeScript 编译器原理
前端·typescript
云端看世界14 分钟前
ECMAScript 运算符怪谈 上
前端·javascript·ecmascript 6
前端涂涂14 分钟前
express的介绍,简单使用
前端
正在脱发中15 分钟前
vue-cropper 遇到的坑 Failed to execute 'getComputedStyle' on 'Window': parameter
前端·vue.js