最近在复习Vue,对比着来学习记录~
在解释问题之前,我们先回顾一下Proxy和Object.defineProperty的相关知识
什么是Proxy?
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 可以理解成在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
语法:
const p = new Proxy(target, handler)
target
:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。handler
:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。注意:
Proxy.revocable(target, handler);
用来创建一个可撤销的代理对象,其返回一个包含了代理对象本身和它的撤销方法revoke的可撤销 Proxy 对象。
一旦某个代理对象被撤销,它将变得几乎完全不可调用,在它身上执行任何的可代理操作都会抛出 TypeError 异常(注意,可代理操作一共有 14 种,执行这 14 种操作以外的操作不会抛出异常)。一旦被撤销,这个代理对象便不可能被直接恢复到原来的状态,同时和它关联的目标对象以及处理器对象都有可能被垃圾回收掉。
var revocable = Proxy.revocable({}, {
get(target, name) {
return "[[" + name + "]]";
}
});
var proxy = revocable.proxy;
proxy.foo; // "[[foo]]"
revocable.revoke();
console.log(proxy.foo); // 抛出 TypeError
proxy.foo = 1 // 还是 TypeError
delete proxy.foo; // 又是 TypeError
typeof proxy // "object",因为 typeof 不属于可代理操作
get()
用于拦截对象的读取属性操作。
get: function(target, property, receiver) {
}
该方法会拦截目标对象的以下操作:
proxy[foo]
和 proxy.bar
Object.create(proxy)[foo]
Reflect.get()
var p = new Proxy({}, {
get: function(target, prop, receiver) {
console.log("called: " + prop);
return 10;
}
});
console.log(p.a); // "called: a"
// 10
set()
设置属性值操作的捕获器
set: function(target, property, value, receiver) {
}
该方法会拦截目标对象的以下操作:
var p = new Proxy({}, {
set: function(target, prop, value, receiver) {
target[prop] = value;
console.log('property set: ' + prop + ' = ' + value);
return true;
}
})
console.log('a' in p); // false
p.a = 10; // "property set: a = 10"
console.log('a' in p); // true
console.log(p.a); // 10
对一个空对象架设了一层拦截,实际上像重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。
var obj = new Proxy({},{
get(target,prop){
console.log(`读取了${prop}`);
return Reflect.get(target, prop);
},
set(target,prop,value){
console.log(`更新了${prop}`);
return Reflect.set(target, prop, value);
}
})
obj.a=1; //更新了a
console.log(obj.a); //读取了a
// 1
注意
:要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
handler 对象的其他方法:
apply
方法拦截函数的调用、call和apply操作。has()
方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。虽然for…in循环也用到了in运算符,但是has()拦截对for…in循环不生效。deleteProperty(target, propKey)
:拦截delete proxy[propKey]的操作,返回一个布尔值。ownKeys(target)
:拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。getOwnPropertyDescriptor(target, propKey)
:拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。preventExtensions(target)
:拦截Object.preventExtensions(proxy),返回一个布尔值。getPrototypeOf(target)
:拦截Object.getPrototypeOf(proxy),返回一个对象。isExtensible(target)
:拦截Object.isExtensible(proxy),返回一个布尔值。setPrototypeOf(target, proto)
:拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。construct(target, args)
:拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。defineProperty()
:用于拦截对对象的 Object.defineProperty() 操作。Object.defineProperty()方法的作用?
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法:
Object.defineProperty(obj, prop, descriptor)
obj
:要定义属性的对象。prop
:要定义或修改的属性的名称或 Symbol 。descriptor
:要定义或修改的属性描述符。属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。value
表示该属性对应的值。默认为 undefinedwritable
属性设置为 false 时,该属性被称为“不可写的”。它不能被重新赋值。试图写入非可写属性不会改变它,也不会引发错误。默认值是falseenumerable
定义了对象的属性是否可以在 for…in 循环和 Object.keys() 中被枚举。默认值是falseconfigurable
特性表示对象的属性是否可以被删除,以及除 value 和 writable 特性外的其他特性是否可以被修改。默认值是false存取描述符
还具有以下可选键值:
get
:属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。默认为 undefined。set
:属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined。如果对象中不存在指定的属性,Object.defineProperty() 会创建这个属性。当描述符中省略某些字段时,这些字段将使用它们的默认值。如果属性已经存在,Object.defineProperty()将尝试根据描述符中的值以及对象当前的配置来修改这个属性。
var o = {}; // 创建一个新对象
// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(o, "a", {
value : 37,
writable : true,
enumerable : true,
configurable : true
});
// 对象 o 拥有了属性 a,值为 37
// 在对象中添加一个设置了存取描述符属性的示例
var bValue = 38;
Object.defineProperty(o, "b", {
// 使用了方法名称缩写(ES2015 特性)
// 下面两个缩写等价于:
// get : function() { return bValue; },
// set : function(newValue) { bValue = newValue; },
get() { return bValue; },
set(newValue) { bValue = newValue; },
enumerable : true,
configurable : true
});
console.log(o.a); // 37
console.log(o.b); // 38
o.a=100
bValue=9;
console.log(o.a); // 100
console.log(o.b); // 9
注意
:直接在get函数中return o.b的话,这里的o.b同时也会调用一次get函数,这样的话会陷入一个死循环;set函数也是同样的道理,因此我们通过一个第三方的变量bValue来防止死循环。
使用点运算符和 Object.defineProperty() 为对象的属性赋值时,数据描述符中的属性默认值是不同的
var o = {};
o.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
value: 1,
writable: true,
configurable: true,
enumerable: true
});
// 另一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同于:
Object.defineProperty(o, "a", {
value: 1,
writable: false,
configurable: false,
enumerable: false
});
如果访问者的属性是被继承的,它的 get 和 set 方法会在子对象的属性被访问或者修改时被调用。如果这些方法用一个变量存值,该值会被所有对象共享。
function myclass() {
}
var value;
Object.defineProperty(myclass.prototype, "x", {
get() {
return value;
},
set(x) {
value = x;
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1
这可以通过将值存储在另一个属性中解决。在 get 和 set 方法中,this 指向某个被访问和修改属性的对象。
function myclass() {
}
Object.defineProperty(myclass.prototype, "x", {
get() {
return this.stored_x;
},
set(x) {
this.stored_x = x;
}
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // undefined
在Vue2
中
//源数据
let person = {
name:'张三',
age:18
}
let p = {}
Object.defineProperty(p,'name',{
configurable:true,
get(){ //有人读取name时调用
return person.name
},
set(value){ //有人修改name时调用
console.log('有人修改了name属性,我发现了,我要去更新界面!')
person.name = value
}
})
这样做的缺点是:
虽然Object.defineProperty能够劫持对象的属性,但是需要对对象的每一个属性进行遍历劫持;
无法检测到直接对对象属性的添加或删除,除非使用如下指令添加
// 监测不到
this.person.sex=女
delete this.person.name
// 监测得到
this.$set(this.person,'sex',女)
// Vue.set(this.person,'sex',女)
this.$delete(this.person.'name')
//Vue.delete(this.person.'name')
// 监测不到
this.person.hobby[0]='学习'
// 监测得到
this.$set(this.person.hobby,0,'逛街')
this.person.hobby.splice(0,1,splice)
在Vue3
中
//源数据
let person = {
name:'张三',
age:18
}
const p = new Proxy(person,{
//有人读取p的某个属性时调用
get(target,propName){
console.log(`有人读取了p身上的${propName}属性`)
return Reflect.get(target,propName)
},
//有人修改p的某个属性、或给p追加某个属性时调用
set(target,propName,value){
console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
Reflect.set(target,propName,value)
},
//有人删除p的某个属性时调用
deleteProperty(target,propName){
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
return Reflect.deleteProperty(target,propName)
}
})
使用Proxy对比Object.defineProperty做数据劫持好处如下:
// 检查得到
person.sex=女
delete person.name
// 可检测
person.hobby[0]='学习'
本篇文章到处结束,如果觉得对你有帮助,记得收藏~