Vue响应式原理中, Object.definePropty有什么缺陷?为什么在Vue3.0采用了Proxy,抛弃了Object.definePropty?
Object.definePropty
该方法允许精确地添加或修改对象的属性,并返回对象。默认情况下此方法添加的属性是不可修改的。
用法:
Object.definePropty(obj,prop,descriptor)返回值:返回更新或者添加属性后的obj对象。
参数:
obj:要定义属性的对象。prop:要添加或修改的属性名。descriptor:属性描述符对象。包含以下属性的对象:
value:属性的值,默认值是undefinedwritable:属性是否可写。默认值是falseenumerable:属性的可枚举性,默认值是false,即属性不可枚举(或者说目标属性在 for…in、Object.keys、JSON.stringify 中是否可遍历)。configurable:属性的可配置性。默认为false,即属性不可被删除。get:获取属性值的函数。set:设置属性值的函数。
注意:
Object.defineProperty设置了getter和setter之后,就不能设置value属性。否则报错:
Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute,
Proxy
Proxy对象创建一个对象的代理,从而实现基本操作的拦截和自定义。
用法:
const _my_proxy = new Proxy(target,handler)参数:
target:需要被代理的对象。通常是引用数据类型(数组,函数,对象等)。handler:一个配置对象,具有以下配置选项。handler.apply()
handler.construct()
handler.defineProperty()
handler.deleteProperty()
handler.get() 拦截对象属性的读写操作。
handler.getOwnPropertyDescriptor()
handler.getPrototypeOf()
handler.has() 拦截
in操作符,判断对象是否包含某个属性。handler.isExtensible()
handler.ownKeys()
handler.preventExtensions()
handler.set() 拦截对象属性的赋值操作。
handler.setPrototypeOf()
Object.definePropty有什么缺陷?
- 无法监听数组的变化。
- 无法监听对象属性的添加或删除
- 性能问题:这个api需要递归遍历对象的所有属性并为其添加
getter和setter,者在对象负责或者嵌套层级较深时会影响性能。
Proxy的优势
- 全面监听对象和数组
- 性能提升:Proxy在对象层级较深时,只在访问属性时才会进行代理,性能更好。
Vue 3.0采用Proxy替代Object.defineProperty,主要因为Proxy能更全面、高效地监听对象和数组的变化,解决了Object.defineProperty在数组监听、属性增删和性能上的缺陷,同时提供了更好的扩展性和兼容性。
Vue的响应式原理
vue的响应式原理是在getter,setter的基础上结合发布-订阅模式实现。
Vue响应式系统:
import Observe from './Observe'
import Compile from './Compile'
export class Vue<D extends Record<string, any>> {
public $el: string
public $data: D
constructor(options: { el: string; data: D }) {
this.$el = options.el
this.$data = options.data
new Observe(this.$data)
this.proxyData(this.$data)
new Compile(this, this.$el)
}
//设置代理,拦截Vue实例的属性,可以直接在Vue实例上访问响应式数据
private proxyData(data) {
for (const [key] of Object.entries(data)) {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(v) {
data[key] = v
},
})
}
}
}
// 工厂函数:返回 Vue 实例 & data 属性的交叉类型
function createApp<D extends Record<string, any>>(options: { el: string; data: D }): Vue<D> & D {
const instance = new Vue(options)
return instance as Vue<D> & D
}
export { createApp }定义一个观察者,观察者负责递归地对响应式数据进行拦截。当数据被设置时,通知订阅了此响应式数据的watcher进行数据更新。观察者代码如下:
import { protoType } from './ArrayProtoType'
import { watchers } from './Watcher'
//对Vue data字段的各个属性进行数据劫持
export default class Observe {
constructor(public data) {
this.walk(data)
}
public walk(data) {
if (typeof data !== 'object' || data == null) return
if (Array.isArray(data)) {
//重置原型对象
Object.setPrototypeOf(data, protoType)
}
Object.keys(data).forEach((key) => {
this.definedReactive(data, key, data[key])
})
}
public definedReactive(data: any, key: string, value: any) {
this.walk(value)
Object.defineProperty(data, key, {
get() {
return value
},
set: (v) => {
if (v == value) return
this.walk(v)
value = v
watchers.forEach((w) => w.update())
},
})
}
}定义订阅者,当订阅的数据发生变化时,操作DOM执行更新:
import type { Vue } from './Vue'
export const watchers = [] as Watcher[]
export default class Watcher {
public vm: Vue<any>
public name: string
public fn: (v: any) => void
constructor(vm: Vue<any>,attr: string, fn: (v: any) => void) {
this.vm = vm
this.name = attr
this.fn = fn
watchers.push(this)
}
public update() {
this.fn(this.vm[this.name])
}
}完整代码: