简单谈谈
Proxy
:在目标对象之前架设一层 拦截
,当对该对象进行 访问/赋值 时,必须先通过这层拦截,从而实现对属性进行 访问/赋值 时的过滤和改写。
一个简单的例子
let obj = new Proxy({}, {
get(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
obj.count = 1;
// setting count!
++obj.count;
// getting count!
// setting count!
// 2
以上实现了对 {}
对象操作时的拦截(通过 obj
对象),也就是说:proxy
为开发者提供更加细粒化对数据的操作,使得开发者可以介入对数据的操作,在不改变原有操作的情况下修改数据流程。
写法
let proxy = new Proxy(target, handler);
target
: 需要被代理的目标对象。handler
: 需要对对象进行代理的行为合集。
使用
属性拦截
let proxy = new Proxy({}, {
get(target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
通过 proxy
的介入,使得对于任何属性的访问都返回特定值,当然也可以自定规则。
未设拦截
let target = {}
let handler = {}
let proxy = new Proxy(target, handler)
proxy.a = 'b'
target.a
// "b"
当不拦截对象(也就是代理行为为空)时,对代理对象的操作可以认为是对原对象进行操作。
常用的行为
参数名词统一解释:
参数名 | 作用 |
---|---|
target |
被代理的对象(也就是原始对象) |
propKey |
访问的键值 |
value |
设置的属性值 |
receiver |
提供者,见注一 |
propDesc |
某个对象属性的描述 |
proto |
某个对象的原型 |
ctx |
函数执行上下文 |
args |
传入的函数参数 |
注一 提供者:比如当访问 proxy.foo
时,receiver
即为 proxy
,大多数情况是代理对象本身,但如果代理对象(A
)是某个对象(B
)的原型的话,当 B
通过原型链访问到 A
时,receiver
为 A
。
可以使用拦截:一共 13
种,如下:
拦截方法名 | 作用 |
---|---|
get(target, propKey, receiver) |
拦截对象属性的读取,返回值。eg: proxy.foo |
set(target, propKey, value, receiver) |
拦截对象属性的设置。 eg: proxy.foo = 1 |
has(target, propKey) |
拦截对象的 in 操作,返回布尔值。 eg: foo in proxy |
deleteProperty(target, propKey) |
拦截 delete 操作,返回布尔值。 eg: delete proxy.foo |
ownKeys(taget) |
拦截对象取键值的操作(注一),返回数组 |
getOwnPropertyDescriptor(target, propKey) |
拦截 Object.getOwnPropertyDescriptor ,返回属性的描述对象。 |
defineProperty(target, propKey, propDesc) |
拦截 Object.defineProperty[s] ,返回布尔值。 |
preventExtensions(target) |
拦截 Object.preventExtensions ,返回布尔值。 |
getPrototypeOf(target) |
拦截 Object.getPrototypeOf ,返回对象。 |
isExtensible(target) |
拦截 Object.isExtensible ,返回布尔值。 |
setPrototypeOf(target, proto) |
拦截 Object.setPrototypeOf ,返回布尔值。 |
apply(target, ctx, args) |
拦截函数的绑定执行上下文的行为,包括函数的调用、call 、apply 操作。 |
construct(target, args) |
拦截函数作为构造函数被调用时的行为。 |
注一:对象的取值操作包括:Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
。
拦截什么?
通过一个简单的实例,来解释不同拦截的具体行为。
Get
拦截对象属性的读取操作。
let sit = {
name: '斑码',
};
let proxy = new Proxy(sit, {
// target:原对象,propKey:引用的键。
get(target, propKey) {
if (propKey in target) {
return target[propKey];
} else {
throw new Error("Prop name \"" + propKey + "\" does not exist.");
}
}
});
proxy.name // "斑码"
proxy.type // 抛错,如果没有代理应该是 undefined
Set
拦截对象属性的设置操作。
let sit = {
name: '斑码',
};
let proxy = new Proxy(sit, {
// target:原对象,propKey:引用的键,value:设置的值。
set(target, propKey, value) {
// 当需要设置的属性不在对象属性中,则报错。
if (!propKey in target) {
throw new Error("Prop name \"" + propKey + "\" does not exist.");
}
return target[propKey] = value;
}
});
proxy.name = "斑马"; // "斑码"
proxy.type = "个人博客"; // 抛错,如果没有代理 sit 对象应该会多一个 type 属性。
apply
拦截函数对象的调用。
let target = function(arr) {
return [1, 2, 3, 4, 5, ...arr];
}
let proxy = new Proxy(target, {
// target:原对象,propKey:this 指向,args:调用时的参数。
apply(target, ctx, args) {
// 获取原来的返回数据。
let targetReturn = target.apply(ctx, args);
// 累加后返回。
return targetReturn.reduce((acc, item) => acc + item);
}
});
proxy([6, 7, 8, 9, 10]); // 55
has
拦截 HasProperty 操作。
// 以 _ 开始的属性为私有属性。
let sit = {
name: "斑码",
_private: true
}
let proxy = new Proxy(sit, {
has(target, propKey) {
if (propKey[0] === '_') {
return false;
}
return propKey in target;
}
});
'_private' in proxy; // false,has 操作拦截后,返回了正确的结果。
construct
拦截函数的实例化操作。
let Sit = function(name) {
this.name = name;
}
let SitProxy = new Proxy(Sit, {
construct(target, args, newTarget) {
console.log("proxy construct");
if(args.length === 0){
args = ["斑码"];
}
return new target(...args);
}
});
let sit = new SitProxy();
// "proxy construct"
sit
// {name: "斑码"}
// construct 操作处理了构造函数参数的问题。
deleteProperty
拦截对象属性的删除操作(delete
)。
// _ 开头的属性为私有属性。
let sit = {
name: "斑码",
_private: true
};
let proxy = new Proxy(sit, {
deleteProperty(target, propKey) {
if (propKey[0] === '_') {
throw new Error(`can't delete private key: ${propKey}`);
}
delete target[propKey];
return true;
}
});
delete sit['_private']; // 报错,deleteProperty 操作不允许删除私有属性。
defineProperty
拦截 Object.defineProperty
操作(包括对属性的设置)。
let sit = {
name: "斑码",
_private: 'test'
};
let proxy = new Proxy(sit, {
defineProperty(target, propKey, descriptor) {
if (propKey[0] === "_") {
throw new Error(`can't add private key: ${propKey}`);
}
Object.defineProperty(target, propKey, descriptor);
return true;
}
})
proxy._private = "test";
// 报错,defineProperty 不允许设置私有属性。
Object.defineProperty(proxy, "_private", {
value: "test"
});
// 报错,defineProperty 不允许设置私有属性。
getOwnPropertyDescriptor
拦截 Object.getOwnPropertyDescriptor
操作,返回对象下对应属性的描述符。
let sit = {
name: "斑码",
_private: 'test'
};
let proxy = new Proxy(sit, {
getOwnPropertyDescriptor(target, propKey) {
if (propKey[0] === "_") {
throw new Error(`can't get private key descriptor: ${propKey}`);
}
return Object.getOwnPropertyDescriptor(target, propKey);
}
})
Object.getOwnPropertyDescriptor(proxy, "name");
Object.getOwnPropertyDescriptor(proxy, "_private");
// 报错,getOwnPropertyDescriptor 不允许获取私有属性的属性描述。
getPrototypeOf
拦截获取对象原型的操作,具体有以下操作:
Object.prototype.__proto__
Object.prototype.isPrototypeOf
Object.getPrototypeOf
Reflect.getPrototypeOf
instanceof
let sit = {
name: "斑码",
_private: 'test'
};
let proxy = new Proxy(sit, {
getPrototypeOf(target, propKey) {
console.log('proxy getPrototypeOf');
return Object.getPrototypeOf(target);
}
})
proxy.__proto__ // "proxy getPrototypeOf"
注: getPrototypeOf
拦截的返回必须为一个合理的原型(对象或是 null
),否则会报错。
isExtensible
拦截 Object.isExtensible
操作。
let proxy = new Proxy({}, {
isExtensible(target) {
console.log("proxy isExtensible");
return true;
}
})
Object.isExtensible(proxy); // "proxy isExtensible"
ownKeys
拦截获取对象键的操作,具体有以下方法:
Object.getOwnPropertyNames
Object.getOwnPropertySymbols
Object.keys
for...in
let sit = {
name: "斑码",
_private: 'test'
};
let proxy = new Proxy(sit, {
ownKeys(target) {
return Object.getOwnPropertyNames(target).filter(key => key[0] !== '_');
}
})
Object.getOwnPropertyNames(proxy); // ["name"],私有属性被 ownKeys 给过滤掉了。
注:
ownKeys
返回的数据并非是最终结果,数组内部分数据会被移除。Object.keys
和for...in
操作会过滤掉:- 目标对象上不存在的属性。
Symbol
值。- 不可遍历(
enumerable = false
)的属性。
ownKeys
的返回必须是字符串或Symbol
值组成的数组,否则会报错。- 若拦截对象的某个属性是不可配置的(
configurable = false
),则返回数组中必须要有该值。 - 如果对象是不可扩展的(
preventExtensions
),此时,必须返回对象下所有的键值,不能多也不能少。
preventExtensions
拦截 Object.preventExtensions
操作,返回布尔值,若返回非布尔值,则自动转换为布尔值。
let proxy = new Proxy({}, {
preventExtensions(target) {
console.log("proxy preventExtensions");
return false;
}
});
Object.preventExtensions(proxy); // "proxy preventExtensions"
setPrototypeOf
拦截 Object.setPrototypeOf
操作,返回布尔值,若返回非布尔值,则自动转换为布尔值。
let proxy = new Proxy({}, {
setPrototypeOf(target) {
throw new Error("can't change prototype");
}
});
Object.setPrototypeOf(proxy, null); // 报错,setPrototypeOf 禁止了对象修改原型。
proxy.__proto__ = null; // 报错
Proxy.revocable
Proxy.revocable
方法返回一个可取消的 Proxy
实例。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo; // 123
revoke()
proxy.foo // TypeError: Revoked
函数返回一个代理对象和取消方法,当执行取消方法时,代理即会被销毁。