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()
比如 修改属性