用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种 “元编程”,即对编程语言进行编程
Proxy 可理解成,在目标对象之前架设一层 “拦截” ,外界对该对象的访问,都必须先通过这层拦截,因此提供了一些机制,可对外界的访问进行过滤和改写。Proxy 原意是 “代理”,用在这表示由它来 “代理” 某些操作,可译为 “代理器”。
let o = {
a: 1,
b: 2
}
let obj = new Proxy(o, {
// target -> 目标对象,在这里是 o对象
// p -> 获取的值,在这里是 obj.a
get(target, p, receiver) {
return 123;
// 在这里做了一层拦截,无论访问 o对象的 a属性或 b属性都返回123
}
let o = {
a: 1,
b: 2
}
let obj = new Proxy(o, {
// target -> 目标对象,在这里是 o对象
// p -> 获取的值,在这里是 obj.a
get(target, p, receiver) {
return target[p];
// 在这里做了一层拦截,无论访问 o对象的 a属性或 b属性都返回123
},
set(target, p, value, receiver) {
console.log(target);
console.log(p);
console.log(value);
}
});
obj.a = 123;
// {a: 1, b: 2}
// a
// 123
ES6原生提供Proxy构造函数,用来生成Proxy实例
let proxy = new Proxy(target, handler);
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler 参数的写法。其中,new Proxy() 表示生成一个 Proxy 实例,target 参数表示所要拦截的目标对象,handler 参数也是一个对象,用来定制拦截行为。
// 就算对象完全是空的,有 “.” 操作就会触发get 方法
var proxy = new Proxy({}, {
get: function (target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
若handler 没设任何拦截,那就等同于直接通向原对象
同一个拦截器函数,可以设置拦截多个操作
对于可设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果
Proxy支持的拦截操作,共13种(写框架用的)
-
get(target, propKey, receiver): 拦截对象属性的读取
比如:proxy.foo 和 proxy[‘foo’] -
set(target, propKey, value, receiver): 拦截对象属性的设置
比如:proxy.foo =v 和 proxy[‘foo’]=v , 返回一个布尔值 -
has(target, propKey): 拦截 propKey in proxy 的操作,返回一个布尔值
-
deleteProperty(target, propKey): 拦截 delete proxy[propKey] 的操作,返回一个布尔值
-
ownKeys(target): 拦截 Object.getOwnPropertyNames(proxy)、
Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys() 的返回结果仅包括目标对象自身的可遍历属性 -
getOwnPropertyDescriptor(target, propKey): 拦截getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
-
defineProperty(target, propKey, propDesc): 拦截 defineProperty(proxy, propKey, propDesc)、defineProperties(proxy, propDesc),返回一个布尔值
-
preventExtensions(target): 拦截 preventExtensions(proxy),返回一个布尔值
-
getPrototypeOf(target): 拦截 getPrototypeOf(proxy),返回一个对象
-
isExtensible(target): 拦截 isExtensible(proxy),返回一个布尔值
-
**setPrototype(target, proto): ** 拦截 setPrototype(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可拦截
-
apply(target, object, args): 拦截Proxy 实例作为函数调用的操作,比如:proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
-
construct(target, args): 拦截Proxy实例作为构造函数调用的操作,比如: new proxy(…args)
deleteProperty 方法用于拦截 delete 操作,若这个方法抛出错误或返回false,当前属性就无法被 delete 命令删除
apply 方法拦截函数的使用、call和apply 操作
get 方法用于拦截某个属性的读取操作
Proxy.revocable()
返回一个可取消的Proxy 实例
let target = {};
let handler = {};
// 当执行 revoke函数后,再访问 Proxy实例,会报错
let {proxy, revoke} = Proxy.revocable(target, handler);
// Proxy.revocable 方法返回一个对象,该对象的 proxy属性是 Proxy实例,
// revoke属性是一个函数,可取消 Proxy实例。
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
Proxy.revocable() 的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问
this 问题
虽然 Proxy可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。
主要原因是:在 Proxy代理的情况下,目标对象内部的 this关键字会指向 Proxy代理
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
// 一旦 proxy代理 target.m,后者内部的 this就是指向 proxy,而不是target
Reflect
与 Proxy对象一样,也是ES6为了操作对象而提供的新API
设计目的
- 将 Object对象的一些明显属于语言内部的方法 (比如:Object.defineProperty),放到 Reflect对象上。现阶段,某些方法同时在 Object和 Reflect对象上部署,未来的新方法将只部署在Reflect对象上
- 修改某些 Object方法的返回结果,让其变得更合理
比如:Object.defineProperty(obj, name, desc)则返回false - 让 Object操作都变成函数行为。某些 Object操作是命令式,比如 name in obj 和 delete obj[name],而Reflect.has(obj, name) 和 Reflect.deleteProperty(obj, name) 让它们变成了函数行为
- Reflect对象的方法与 Proxy对象的方法一一对应,只要是 Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的 Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管 Proxy怎么修改默认行为,总可以在 Reflect上获取默认行为