在 Vue 2 中,Object.defineProperty 被用于实现数据的响应式,通过数据劫持来检测变化。而在 Vue 3 中,Proxy 取而代之,提供了更高效的响应式系统,允许直接拦截对象操作。
Object.defineProperty
概述
Object.defineProperty()方法用于在指定对象上定义一个新属性,或者修改一个对象的现有属性,并 返回该对象 。( 原地更改对象 )
语法
Object.defineProperty(obj, prop, descriptor)
obj:要在其上定义属性的对象。prop:要定义或修改的属性的名称。descriptor:一个包含属性描述符的对象,决定属性的特征。
属性描述符descriptor
属性描述符是一个对象,包含以下可选键:
value:属性的值。writable:如果为true,属性的值可以被赋值操作改变。get:一个函数,当属性被读取时调用,函数返回值就是属性的值。set:一个函数,当属性被赋值时调用,接收赋值操作传入的值。configurable:如果为true,属性可以被删除,get和set访问器可以被修改。enumerable:如果为true,表示可以遍历,属性出现在for...in循环中。
示例
const obj = {
a:1,
b:2,
c:{
a:1,
b:2
}
}
let v = obj.a
// 在外面获取a的初始值,是为了防止在get(a)时return了obj[a],导致死循环
Object.defineProperty(obj, 'a', {
get(){
console.log('获取了');
return v
},
set(value){
if(value !== v){
console.log('设置了');
v = value
}
}
})
console.log(obj.a) // 获取了 1
obj.a = 3 // 设置了
console.log(obj.a) // 获取了 3
const obj = {
a: 1,
b: 2,
c: {
a: 1,
b: 2
}
}
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
// 深度劫持
function observe(obj) {
if (isObject(obj)) {
for (let key in obj) {
let v = obj[key] // 避免在get时return obj[key]导致死循环
if (isObject(v)) {
observe(v) // 递归进行深度劫持
}
Object.defineProperty(obj, key, {
get() {
console.log('获取了');
return v
},
set(value) {
if (value !== v) {
console.log('设置了');
v = value
}
}
})
}
}
}
observe(obj)
console.log(obj.a); // 获取了 1
console.log(obj.c.a); // 获取了 获取了 1
注意事项
get和set访问器中this关键字指向定义属性的上下文,通常是一个属性描述符对象,而不是目标对象。- 属性必须是
configurable: true才能被Object.defineProperty()修改或使用delete删除。
Proxy
概述
Proxy对象用于创建一个对象的代理,从而在访问对象前可以进行某种操作。
语法
new Proxy(target, handler)
target:一个对象,它是代理对象的原始对象。handler:一个对象,其属性是当操作代理对象时定义代理的行为的函数。
处理程序(Handler)
处理程序对象可以定义以下11个方法:
注意:receiver 参数是 Reflect.get 方法的第三个可选参数,它的作用是:
- 当你访问的对象属性实际上是通过原型链从另一个对象继承的访问器属性(即有一个
getter函数)时,receiver参数指定了调用这个getter函数时this应该指向的对象。
在大多数简单的情况下,你可能不需要使用 receiver 参数,因为默认情况下,访问器属性的 getter 会在访问它的那个对象上被调用。
但是,如果你需要确保在访问继承的访问器属性时,this 绑定到特定的对象上,那么 receiver 参数就非常有用了。
const parent = {
_prop: 'value on parent',
get prop() {
console.log('get prop', this._prop);
return this._prop;
}
};
const child = Object.create(parent);
child._prop = 'value on child';
// 使用 Reflect.get 访问 child 对象的 prop 属性,确保 getter 调用时 this 指向 child
const value = Reflect.get(child, 'prop', child);
console.log(value); // "get prop value on child"
get(target, prop, receiver):获取属性时调用。set(target, prop, value, receiver):设置属性时调用。has(target, prop):检查属性是否存在时调用。deleteProperty(target, prop):删除属性时调用。ownKeys(target):获取对象所有键时调用。getOwnPropertyDescriptor(target, prop):获取属性描述符时调用。defineProperty(target, prop, descriptor):定义属性时调用。preventExtensions(target):阻止对象扩展时调用。getPrototypeOf(target):获取对象原型时调用。isExtensible(target):检查对象是否可扩展时调用。setPrototypeOf(target, proto):设置对象原型时调用。
示例
const obj = {
a: 1,
b: 2,
c: {
a: 1,
b: 2
}
}
const proxy = new Proxy(obj, {
get(target, key) {
let temp = Reflect.get(target, key)
console.log(`正在读取${key}值,为:`);
return temp
},
set(target, key, value) {
console.log(`正在设置${key}`);
if (value !== Reflect.get(target, key)) {
Reflect.set(target, key, value);
}
},
deleteProperty(target, key){
console.log(`删除属性了${key}`);
}
})
console.log(proxy.a); // 正在读取a值,为: 1
proxy.a = 10 // 正在设置a
console.log(proxy.a); // 正在读取a值,为: 10
delete proxy.a // 删除属性了a
const obj = {
a: 1,
b: 2,
c: {
a: 1,
b: 2
}
}
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
function observe(obj){
const proxy = new Proxy(obj, {
get(target, key) {
let temp = Reflect.get(target, key)
if(isObject(temp)){
temp = observe(temp) // 只有在访问到对象的时候才会递归
}
console.log(`正在读取${key}值,为:`);
return temp
},
set(target, key, value) {
console.log(`正在设置${key}`);
if (value !== Reflect.get(target, key)) {
Reflect.set(target, key, value);
}
},
deleteProperty(target, key){
console.log(`删除属性了${key}`);
}
})
return proxy
}
let p = observe(obj)
console.log(p.a); // 正在读取a值,为: 1
p.a = 12 // 正在设置a
console.log(p.a); // 正在读取a值,为: 12
console.log(p.c.a); // 正在读取c值,为: 正在读取a值,为: 1
p.c.a = 13 // 正在读取c值,为: 正在设置a
console.log(p.c.a); // 正在读取c值,为: 正在读取a值,为: 13
注意事项
- 代理对象的
delete、deleteProperty、has、set和defineProperty操作可以拦截对象的相应操作。 - 代理可以创建一个对象的“安全”版本,隐藏内部的实现细节。
- 代理可以用于懒加载或延迟初始化对象的属性。
对比 Object.defineProperty 和 Proxy
Object.defineProperty用于在对象上定义或修改单个属性。Proxy可以用于整个对象,为对象的所有操作提供自定义行为。Object.defineProperty实现对象的深度监听,需要一次性递归到底。对于层级比较深的数据来说,计算量比较大。Object.defineProperty无法监听新增属性/删除属性, 因为监听在created之前就完成了。(但是vue2.0提供了另外的api,分别是Vue.set和Vue.delete)Proxy可以监听新增属性/删除属性等。- 使用
Object.defineProperty可以修改对象的现有属性(就地更改),而Proxy允许你从头开始捕获和自定义任何操作。