Daotin

proxy代理

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

Proxy用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。

ES6原生提供了Proxy构造函数,用来生成Proxy实例。

javascript
var proxy = new Proxy(target, handler);
  • target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler: 一个通常以函数(钩子:“traps”,即拦截操作的方法)作为属性的对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。(比如 get 钩子用于读取 target 属性,set 钩子写入 target 属性等等。)
  • p 是代理后的对象。当外界每次对 p 进行操作时,就会执行 handler 对象上的一些方法。

首先看handler是空对象(没有任何钩子)的情况:

javascript
// 在以下例子中,我们target使用了一个原生 JavaScript 对象,所有对p的操作都直接转发给 target上。
let target = {};
let p = new Proxy(target, {});

p.a = 37;   // 操作转发到目标

console.log(target.a);    // 37. 操作已经被正确地转发
💡我们可以看到,没有任何钩子,proxy 是一个 target 的透明包装. Proxy 是一种特殊的“奇异对象”。它没有自己的属性。如果 handler 为空,则透明地将操作转发给 target。

再看handler有一个get钩子的示例:

javascript
// 在以下简单的例子中,当对象中不存在属性名时,默认返回值为 37。下面的代码以此展示了 get handler 的使用场景。
const handler = {
    get: function(obj, prop) {
        return prop in obj ? obj[prop] : 37;
    }
};

const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b);      // 1, undefined
console.log('c' in p, p.c); // false, 37

Proxy对象的所有用法,都是上面的这种形式。不同的只是handle参数的写法。

其中new Proxy用来生成Proxy实例,target是表示所要拦截的对象,handle是用来定制拦截行为的对象。

有哪些对对象的基本操作的拦截操作?

Proxy共有13种劫持操作,handler代理的一些常用的方法有如下几个:

javascript
get(target, propKey, receiver)//拦截对象属性的读取,比如proxy.foo和proxy['foo']。

set(target, propKey, value, receiver)//拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。

has(target, propKey) //当使用in判断属性是否在proxy代理对象里面时,会触发has

deleteProperty(target, propKey)//当使用delete去删除对象里面的属性的时候,拦截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),返回属性的描述对象。

defineProperty(target, propKey, propDesc)

//拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
preventExtensions(target)//拦截Object.preventExtensions(proxy),返回一个布尔值。

getPrototypeOf(target)//拦截Object.getPrototypeOf(proxy),返回一个对象。

isExtensible(target)//拦截Object.isExtensible(proxy),返回一个布尔值。

setPrototypeOf(target, proto)

//拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
apply(target, object, args)

//拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
construct(target, args)//拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
trapsdescription
get获取某个key值
set设置某个key值
has使用in操作符判断某个key是否存在
apply函数调用,仅在代理对象为function时有效
ownKeys获取目标对象所有的key
construct函数通过实例化调用,仅在代理对象为function时有效
isExtensible判断对象是否可扩展,Object.isExtensible的代理
deleteProperty删除一个property
defineProperty定义一个新的property
getPrototypeOf获取原型对象
setPrototypeOf设置原型对象
preventExtensions设置对象为不可扩展
getOwnPropertyDescriptor获取一个自有属性 (不会去原型链查找) 的属性描述

每种方法的示例用法如下:

一篇彻底理解Proxy | 建议收藏 - 掘金

每个方法的参数可以参考MDN:

Proxy - JavaScript | MDN

💡注意每个方法的返回值。比如set返回true表示成功。

Proxy 对象兼容性

Photo

Reflect 对象

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handlers 的方法相同。可简化的创建 Proxy。

Reflect 对象兼容性

Photo

以前的内部方法,比如[[Get]],[[Set]] 等等都只是规范,不能直接调用。

Reflect 对象使调用这些内部方法成为可能。它的方法是内部方法的最小包装。

这是 Reflect 执行相同操作和调用的示例:

Photo

对于每个可被 Proxy 捕获的内部方法,Reflect 都有一个对应的方法,其名称和参数与 Proxy 钩子相同。

因此,我们可以用 Reflect 来将操作转发到原始对象。

就是说,一切都很简单:如果钩子想要将调用转发给对象,则只需使用相同的参数调用 Reflect.<method> 就足够了。

javascript
let user = {
  name: "John",
};

user = new Proxy(user, {
  get(target, prop, receiver) {
    alert(`GET${prop}`);
    return Reflect.get(target, prop, receiver); // (1)
  },
  set(target, prop, val, receiver) {
    alert(`SET${prop}=${val}`);
    return Reflect.set(target, prop, val, receiver); // (2)
  }
});

let name = user.name; // shows "GET name"
user.name = "Pete"; // shows "SET name=Pete"

参考连接:

Proxy 和 Reflect - 掘金

Reflect的每个方法使用示例:

程序员不得不会的Reflect,有多少人不知道? - 掘金

可取消的 Proxy

假设我们有一个资源,并且想随时关闭对该资源的访问。

我们可以做的是将其包装成可撤销的代理,而没有任何钩子。这样的代理会将操作转发给对象,我们可以随时将其禁用。

javascript
let {proxy, revoke} = Proxy.revocable(target, handler)

该调用返回一个带有 proxy 和 revoke 函数的对象以将其禁用。

这是一个例子:

javascript
let object = {
  data: "Valuable data"
};

let {proxy, revoke} = Proxy.revocable(object, {});

// proxy 正常工作
alert(proxy.data); // Valuable data

// 之后某处调用
revoke();

// proxy 不再工作(已吊销)
alert(proxy.data); // Error