Vue3 为什么要用 Proxy 代替 Object.defineProperty 实现响应式

Object.defineProperty 劫持数据

只是对对象的属性进行劫持

  • 无法监听新增属性和删除属性,需要使用 vue.set, vue.delete
  • 深层对象的劫持需要一次性递归
1 var obj = {
2   a: 1,
3   o: {
4     b: 2,
5     o1: {}
6   }
7 }
  • 劫持数组时需要重写覆盖部分 Array.prototype 原生方法

Proxy 劫持数据

真正的对对象本身进行劫持

  • 可以监听到对象新增删除属性
  • 只在 getter 时才对对象的下一层进行劫持(优化了性能)
  • 能正确监听原生数组方法
  • 无法 polyfill 存在浏览器兼容问题

Object.defineProperty 实现响应式

 1 function defineReactive(target, key, value) {
 2     observer(value) // 对 value 深层监听
 3     
 4     Object.defineProperties(target, key, {
 5         get() {
 6             // dep.addSubs(watcher) // 添加到监听队列
 7             return value
 8         },
 9         set(newValue) {
10             if (newValue !== value) {
11                 observer(newValue) // 再次劫持新 value
12                 
13                 value = newValue
14                 // dep.notify() // 通知依赖触发监听队列的更新
15             }
16         }
17     })
18 }
19 
20 function observer(target) {
21   if (typeof target !== 'object' || !target) {
22     return target
23   }
24   
25   if (Array.isArray(target)) {
26     target.__proto__ = newArrProto
27   }
28   
29   for (let key of target) {
30     defineReactive(target, key, target[key])
31   }
32 }
33 
34 const oldArrProto = Array.prototype
35 
36 const newArrProto = Object.create(oldArrProto)
37 ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(methodName => {
38     newArrProto[methodName] = function(...args) {
39         // dep.notify()
40         oldArrProto[methodName].apply(this, args)
41     }
42 })

Dep 和 Watcher 具体实现可以参考之前的文章 实现响应式原理

Object.defineProperty 缺点

  • 无法监听新增属性和删除属性
    需要使用 vue.set, vue.delete
  • 深层对象的劫持需要一次性递归
 1 function observer(target) {
 2   ...
 3   for (let key of target) {
 4     defineReactive(target, key, target[key])
 5   }
 6 }
 7 
 8 function defineReactive(target, key, value) {
 9   observer(target) // 首次监听时就对 value 的属性进行递归
10   ...
11 }
  • 监听原生数组的部分方法需要重写覆盖 Array.prototype中['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] 会改变原数组的原生方法不会被 Object.defineProperty 劫持,需要重新写数组的原生方法添加更新触发

Proxy 实现响应式

 1 function reactive(target = {}) {
 2     if (typeof target !== 'object' || target == null) {
 3         return target
 4     }
 5     
 6     const proxyConfig = {
 7         get(target, key, receiver) {
 8             const ownKeys = Reflect.ownKeys(target)
 9             if (ownKeys.includes(key)) {
10               // dep.subs(watcher) // 添加监听
11             }
12             const result = Reflect.get(target, key, receiver)
13             return reactive(result) // 只在 getter 时才再次劫持
14         },
15         set(target, key, val, receiver) {
16           if (val === target[key]) {
17             return
18           }
19             
20             const ownKeys = Reflect.ownKeys(target)
21             if (ownKeys.includes(key)) {
22               // 已有值
23             } else {
24               // 新增值
25             }
26             
27             const result = Reflect.set(target, key, val, receiver)
28             // dep.noitfy() // 通知监听队列进行更新
29             return result
30         },
31         deleteProperty(target, key) {
32             const result = Reflect.deleteProperty(target, key)
33             return result
34         }
35     }
36     
37     const observed = new Proxy(target, proxyConfig)
38     return observed
39 }

Proxy 解决的问题

  • 可以监听到对象新增删除属性
  • 只在 getter 时才对对象的下一层进行劫持(优化了性能)
1         get(target, key, receiver) {
2             const ownKeys = Reflect.ownKeys(target)
3             if (ownKeys.includes(key)) {
4               // dep.subs(watcher) // 添加监听
5             }
6             const result = Reflect.get(target, key, receiver)
7             return reactive(result) // 只在 getter 时才再次劫持
8         },
  • 能正确监听原生数组方法

总结

Object.defineProperty 是对对象属性的劫持
Proxy 是对整个对象劫持

Object.defineProperty 无法监听新增和删除
Object.defineProperty 无法监听数组部分方法需要重写
Object.defineProperty 性能不好要对深层对象劫持要一次性递归

Proxy 能正确监听数组方法
Proxy 能正确监听对象新增删除属性
Proxy 只在 getter 时才进行对象下一层属性的劫持 性能优化
Proxy 兼容性不好

Object.defineProperty 和 Proxy
在 getter 时进行添加依赖 dep.addSub(watcher) 比如 绑定 view 层,在函数中使用
在 setter 时触发依赖通知 dep.notify() 比如 修改属性

posted @ 2022-09-28 17:03  强者之途  阅读(201)  评论(0编辑  收藏  举报
/* 看板娘 */