为什么要使用代理?

之所以使用代理,就是不希望用户能够直接访问某个对象,直接操作对象的某个成员(因为这样是不可控的,我们不知道用户在访问操作哪一个对象)

通过代理,我们可以拦截用户的访问(称为数据劫持),拦截住后我们就可以对数据进行一些处理,比如做一些数据的验证,之后再允许用户的访问操作(因为我们拦截了用户的每一次访问,这样用户操作对象就完全是在我们可控的范围内)

简单来说,就是我们希望用户在访问对象时我们能够清除的知道用户在访问什么并且能够在中间做一些我们自己的操作

Proxy是什么?

ProxyES6 中新增的一个构造函数,也可以叫类,通过new操作符调用使用。

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

需要注意的是,Proxy并没有prototype原型对象

官方说明是因为 Proxy 构造出来的实例对象仅仅是对目标对象的一个代理,所以 Proxy 在构造过程中是不需要 prototype 进行初始化的

其他构造函数之所以需要 prototype,是因为构造出来的对象需要一些初始化的成员,所以将这些成员定义到了 protoype

基础语法

const proxyTarget = new Proxy(target, handler)

参数:

  • target:Proxy 会对 target 对象进行包装。它可以是任何类型的对象,包括内置的数组,函数甚至是另一个代理对象。

  • handler:它是一个对象,它的属性提供了某些操作发生时所对应的处理函数。

    一个空的 handler 参数将会创建一个与被代理对象行为几乎完全相同的代理对象。通过在 handler 对象上定义一组处理函数,你可以自定义被代理对象的一些特定行为。

返回值:

  • proxyTarget :经过Proxy包装后的target对象

基础使用:

const obj = {
name: 'Ailjx'
}
const proxyTarget = new Proxy(obj, {})
console.log(proxyTarget);

在这里插入图片描述

handler处理函数

Proxy代理的灵魂就在于它的第二个参数:handler对象,在这个对象内我们可以定义一些处理函数来进行数据劫持,从而实现一些额外的操作

1.get() 拦截对象属性的读取操作

handler.get() 方法用于拦截对象的读取属性操作

语法:

var proxyTarget = new Proxy(target, {
get: function(target, property, receiver) {}
});

参数:

以下是传递给 get 方法的参数

  • target:目标对象。
  • property:被获取的属性名。
  • receiver:Proxy 或者继承 Proxy 的对象

返回值:

get 方法可以返回任何值,这些返回值就是用户真正获取到的属性值

使用:

const obj = {
name: "Ailjx",
};

var proxy = new Proxy(obj, {
get: function (target, property, receiver) {
console.log("你访问的属性为:", property);
console.log(receiver);
return "My name is " + target.name;
},
});

console.log(proxy.name);

在这里插入图片描述

2.set() 拦截对象属性的修改/设置操作

handler.set() 方法是设置属性值操作的捕获器

语法:

const p = new Proxy(target, {
set: function(target, property, value, receiver) {}
});

参数:

下面的参数将会传递给 set() 方法

  • target:目标对象

  • property:将被设置的属性名

  • value:新属性值。

  • receiver:最初被调用的对象。通常是 proxy 本身,但 handlerset 方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是 proxy 本身)

    假设有一段代码执行 obj.name = "Ailjx"obj 不是一个 proxy,且自身不含 name 属性,但是它的原型链上有一个 proxy,那么,那个 proxyset() 处理器会被调用,而此时,obj 会作为 receiver 参数传进来

返回值:

set() 方法应当返回一个布尔值

  • 返回 true 代表属性设置成功
  • 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常

使用:

const obj = {
name: "Ailjx",
};

const handler = {
set(target, property, value) {
if (property === "name" && typeof value !== "string") {
console.log("姓名必须是字符串!");
} else {
target.name = value;
return true;
}
},
};

const proxy1 = new Proxy(obj, handler);

proxy1.name = 1;
console.log(proxy1.name);

proxy1.name = "Chen";
console.log(proxy1.name);

在这里插入图片描述

3.deleteProperty()拦截对象属性的删除操作

handler.deleteProperty() 方法用于拦截对对象属性的删除操作

语法:

var p = new Proxy(target, {
deleteProperty: function(target, property) {}
});

参数:

deleteProperty方法将会接受以下参数。

  • property:待删除的属性名。

返回值:

deleteProperty 必须返回一个 Boolean 值,表示了该属性是否被成功删除

使用:

const obj = {
name: "Ailjx",
};
var p = new Proxy(obj, {
deleteProperty: function (target, prop) {
delete target[prop];
console.log("你删除了" + prop + "属性");
return true;
},
});
console.log(p.name); // Ailjx
delete p.name; // 你删除了name属性
console.log(p.name); // undefined

在这里插入图片描述

更多处理函数

handler对象内还有以下处理函数:

  • defineProperty():拦截对对象的 Object.defineProperty() 操作
  • apply(): 拦截函数的调用
  • getOwnPropertyDescriptor():Object.getOwnPropertyDescriptor 调用劫持
  • getPrototypeOf() :拦截对象原型的读取操作
  • isExtensible():拦截对对象的 Object.isExtensible()
  • ownKeys():Object.getOwnPropertyNamesObject.getOwnPropertySymbols的调用劫持
  • preventExtensions():对Object.preventExtensions()的拦截
  • setPrototypeOf]():拦截 Object.setPrototypeOf()

可撤销代理

Proxy.revocable() 方法可以用来创建一个可撤销的代理对象

// 创建可撤销的代理
const revocable = Proxy.revocable({},{
get(target, name) {
return "[[" + name + "]]";
},
}
);

console.log(revocable);

在这里插入图片描述 Proxy.revocable() 方法具有和Proxy一样的两个参数:target目标对象和handler对象

但它的返回值有点特殊,它返回一个包含了代理对象本身和它的撤销方法的可撤销 Proxy 对象,其结构为:

{"proxy": proxy, "revoke": revoke}
  • proxy:表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉

  • revoke:撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象

    一旦某个代理对象被撤销,它将变得几乎完全不可调用,在它身上执行任何的可代理操作都会抛出 TypeError 异常(可代理操作指的就是我们在handler对象函数属性上能拦截到的操作,一共有14种,执行这14种以外的情况不会报错)

    一旦被撤销,这个代理对象便不可能被直接恢复到原来的状态,同时和它关联的目标对象以及处理器对象都有可能被垃圾回收掉。再次调用撤销方法 revoke() 则不会有任何效果,但也不会报错

示例:

// 创建可撤销的代理
const revocable = Proxy.revocable(
{},
{
get(target, name) {
return "[[" + name + "]]";
},
}
);

// 获取创建的代理
const proxy = revocable.proxy;
proxy.foo; // "[[foo]]"

// 撤销代理
revocable.revoke();
// 代理撤销后,可代理操作将不能再被使用
console.log(proxy.foo); // 抛出 TypeError
proxy.foo = 1; // 还是 TypeError
delete proxy.foo; // 又是 TypeError

typeof proxy; // "object",因为 typeof 不属于可代理操作

这种机制非常有利于一种情况,就是在我们使用一些不信任的第三方库时候。如果必须向一个自己不信任的库传递一个函数,这时候当然不能直接将自己的函数传递过去,我们可以向其传递一个可以撤销的代理,这样同样可以达到效果,当使用完这个库,我们就将这个代理撤销。保证了安全性