- 概述
- proxy对象的使用
- handler的方法
- get(target, propKey, receiver)
- set(target, propKey, value, receiver)
- has(target, propKey)
- deleteProperty(target, propKey);
- ownKeys(target)
- getOwnPropertyDescriptor(target, propKey)
- defineProperty(target, propKey, descriptor)
- apply(target, thisArg, argumentsList)
- construct(target, argumentsList, newTarget)
- getPrototypeOf(target)
- isExtensible(target)
- preventExtensions(target)
- setPrototypeOf(target, newPrototype)
- Proxy.revocable()
- this 问题
- 实例:Web 服务的客户端
- handler的方法
概述
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (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
proxy对象的使用
let p = new Proxy(target, handler);
target
用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler proxy的处理程序对象
一个对象,其属性是当执行一个操作时定义代理的行为的函数。
handler的方法
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/apply
参考 MDN
get(target, propKey, receiver)
target 目标对象,propKey 访问的key,receiver Proxy或者继承Proxy的对象
return any; 返回一个任意值
拦截对象属性的读取
set(target, propKey, value, receiver)
target 目标对象,propKey 访问的key,value 设置的值,receiver Proxy或者继承Proxy的对象
return any; 返回一个任意值
拦截对象属性的设置
has(target, propKey)
target 目标对象,propKey 可以是字符串或symbol 类型
return boolean;
方法可以看作是针对 in 操作的钩子.
var obj = { a: 1 };
a in obj // true
deleteProperty(target, propKey);
target 目标对象,propKey 访问的key
返回一个boolean 表示是否删除成功
拦截对对象属性的 delete 操作,返回false属性不可删除
ownKeys(target)
target 目标对象
返回一个可枚举对象
该拦截器可以拦截以下操作::
- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
-
getOwnPropertyDescriptor(target, propKey)
target 目标对象,propKey 访问的key
返回属性名称的描述
方法是 Object.getOwnPropertyDescriptor() 的拦截。defineProperty(target, propKey, descriptor)
target 目标对象,propKey key的名称,属性描述符
方法必须以一个 Boolean 返回,表示定义该属性的操作成功与否
该方法会拦截目标对象的以下操作 : Object.defineProperty()
- Reflect.defineProperty()
-
apply(target, thisArg, argumentsList)
target 目标对象 thisArg 被调用时的上下文对象 argumentsList 参数列表
方法可以返回任何值
拦截函数调用construct(target, argumentsList, newTarget)
target 目标对象,argumentsList 参数列表, 最初被调用的构造函数
方法必须返回一个对象
方法用于拦截new 操作符. 为了使new操作符在生成的Proxy对象上生效,用于初始化代理的目标对象自身必须具有[[Construct]]内部方法(即 new target 必须是有效的)。getPrototypeOf(target)
target 目标对象
方法必须返回一个对象或null,不能返回其他值,会抛出异常。
方法拦截Object.getPrototypeOf方法。isExtensible(target)
target 目标对象
方法必须返回一个Boolean或一个可以转换为Boolean的值
该方法会拦截目标对象的以下操作: Object.isExtensible()
检查方法是否是可扩展的,可扩展的返回true,密封,冻结,不可扩展返回falseReflect.isExtensible()
preventExtensions(target)
target目标对象
返回一个boolean
拦截 Object.preventExtensions()返回一个布尔值setPrototypeOf(target, newPrototype)
target 目标对象, newPrototype 新的原型
如果成功修改了[[Prototype]], setPrototypeOf 方法返回 true,否则返回 false.
方法主要用来拦截 Object.setPrototypeOf().
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
Proxy.revocable
方法返回一个对象,该对象的proxy
属性是Proxy
实例,revoke
属性是一个函数,可以取消Proxy
实例。上面代码中,当执行revoke
函数之后,再访问Proxy
实例,就会抛出一个错误。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
。
下面是一个例子,由于this
指向的变化,导致 Proxy 无法代理目标对象。
const _name = new WeakMap();
class Person {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this);
}
}
const jane = new Person('Jane');
jane.name // 'Jane'
const proxy = new Proxy(jane, {});
proxy.name // undefined
上面代码中,目标对象jane
的name
属性,实际保存在外部WeakMap
对象_name
上面,通过this
键区分。由于通过proxy.name
访问时,this
指向proxy
,导致无法取到值,所以返回undefined
。
此外,有些原生对象的内部属性,只有通过正确的this
才能拿到,所以 Proxy 也无法代理这些原生对象的属性。
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);
proxy.getDate();
// TypeError: this is not a Date object.
上面代码中,getDate
方法只能在Date
对象实例上面拿到,如果this
不是Date
对象实例就会报错。这时,this
绑定原始对象,就可以解决这个问题。
const target = new Date('2015-01-01');
const handler = {
get(target, prop) {
if (prop === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, prop);
}
};
const proxy = new Proxy(target, handler);
proxy.getDate() // 1
实例:Web 服务的客户端
Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。
const service = createWebService('http://example.com/data');
service.employees().then(json => {
const employees = JSON.parse(json);
// ···
});
上面代码新建了一个 Web 服务的接口,这个接口返回各种数据。Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截就可以了。
function createWebService(baseUrl) {
return new Proxy({}, {
get(target, propKey, receiver) {
return () => httpGet(baseUrl+'/' + propKey);
}
});
}
同理,Proxy 也可以用来实现数据库的 ORM 层。