阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 响应式系统重构(Proxy替代defineProperty)

响应式系统重构(Proxy替代defineProperty)

作者:陈川 阅读数:30889人阅读 分类: Vue.js

响应式系统重构(Proxy替代defineProperty)

Vue.js 2.x 使用 Object.defineProperty 实现响应式系统,而 Vue 3 则改用 Proxy 重构了整个响应式机制。这种改变带来了性能提升和功能增强,同时也解决了 defineProperty 的一些固有缺陷。

defineProperty 的局限性

Object.defineProperty 存在几个关键问题:

  1. 无法检测属性的添加或删除
const obj = {}
Object.defineProperty(obj, 'a', {
  get() { return this._a },
  set(val) { 
    console.log('set a')
    this._a = val 
  }
})
obj.a = 1 // 触发 set
obj.b = 2 // 不会触发任何响应
  1. 数组变异方法需要特殊处理
const arr = []
Object.defineProperty(arr, 'length', { /* ... */ })
arr.push(1) // 不会触发 length 的 setter
  1. 性能开销较大,需要递归遍历对象的所有属性进行转换。

Proxy 的优势

ES6 的 Proxy 可以完美解决这些问题:

const handler = {
  get(target, key) {
    console.log(`Get ${key}`)
    return Reflect.get(target, key)
  },
  set(target, key, value) {
    console.log(`Set ${key} = ${value}`)
    return Reflect.set(target, key, value)
  },
  deleteProperty(target, key) {
    console.log(`Delete ${key}`)
    return Reflect.deleteProperty(target, key)
  }
}

const proxy = new Proxy({}, handler)
proxy.a = 1 // 输出 "Set a = 1"
proxy.a     // 输出 "Get a"
delete proxy.a // 输出 "Delete a"

Vue 3 的响应式实现

Vue 3 的 reactive 实现核心:

function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      track(target, key) // 依赖收集
      const res = Reflect.get(target, key, receiver)
      if (typeof res === 'object' && res !== null) {
        return reactive(res) // 深层响应
      }
      return res
    },
    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
    }
  }
  return new Proxy(target, handler)
}

性能对比

  1. 初始化性能

    • defineProperty: 需要递归遍历所有属性
    • Proxy: 只在访问时处理,惰性转换
  2. 内存占用

    • defineProperty: 需要为每个属性创建闭包存储依赖
    • Proxy: 整个对象共享一个处理器
  3. 数组处理

const arr = reactive([1, 2, 3])
arr.push(4) // 正常触发响应
arr.length = 10 // 也能触发响应

实际应用场景

  1. 动态属性
const state = reactive({})
// 可以动态添加响应式属性
state.newProp = 'value' 
  1. Map/Set 支持
const map = reactive(new Map())
map.set('key', 'value') // 响应式更新
  1. 更好的TS支持
interface State {
  count: number
  user?: {
    name: string
  }
}
const state: State = reactive({
  count: 0
})
// 类型安全访问
state.count++

兼容性考虑

虽然 Proxy 是现代浏览器的特性,但 Vue 3 提供了降级方案:

function createReactive(target) {
  return ProxySupported 
    ? new Proxy(target, handler)
    : fallbackDefineProperty(target)
}

对于需要支持 IE 的场景,可以使用 @vue/compat 兼容版本。

响应式API的演进

Vue 3 提供了一系列新的响应式API:

import { reactive, ref, computed, watchEffect } from 'vue'

const count = ref(0)
const double = computed(() => count.value * 2)
const state = reactive({ list: [] })

watchEffect(() => {
  console.log(`count is ${count.value}`)
})

与Vue 2的对比

  1. 数组检测
// Vue 2
this.$set(this.arr, index, value)

// Vue 3
arr[index] = value // 直接赋值即可
  1. 嵌套对象
// Vue 2
this.$set(this.obj, 'nested', {})

// Vue 3
obj.nested = {} // 自动响应

源码解析

简化版的依赖收集实现:

const targetMap = new WeakMap()

function track(target, key) {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  dep.add(activeEffect)
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const effects = depsMap.get(key)
  effects && effects.forEach(effect => effect.run())
}

最佳实践

  1. 大型对象处理
// 避免一次性响应式化大型对象
const bigData = shallowReactive({ /* 大数据 */ })
  1. 性能敏感场景
// 使用 markRaw 跳过响应式转换
import { markRaw } from 'vue'
const nonReactive = markRaw({ shouldNotTrack: true })
  1. 组合式函数
function useCounter() {
  const count = ref(0)
  const double = computed(() => count.value * 2)
  function increment() {
    count.value++
  }
  return { count, double, increment }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌