- 概述
- 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 // 123revoke();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() // falseproxy.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 层。
