Vue3中的Proxy代理

Daotin 于 2021-11-25 发布 编辑
  1. 有哪些对对象的基本操作的拦截操作?
  2. Proxy 对象兼容性
  3. Reflect 对象
  4. 可取消的 Proxy

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

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

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

var proxy = new Proxy(target, handler);

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

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

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

console.log(target.a);    // 37. 操作已经被正确地转发

我们可以看到,没有任何钩子,proxy 是一个 target 的透明包装. Proxy 是一种特殊的“奇异对象”。它没有自己的属性。如果 handler 为空,则透明地将操作转发给 target

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

// 在以下简单的例子中,当对象中不存在属性名时,默认返回值为 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代理的一些常用的方法有如下几个:

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)。

traps description
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:handler_对象的方法

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

Proxy 对象兼容性

Reflect 对象

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

Reflect 对象兼容性

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

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

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

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

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

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

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

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

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

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

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

这是一个例子:

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

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

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

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

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

(完)