代理基础:
介绍:给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。
用作目标对象的替身,但又完全独立于目标对象。目标对象既可以直接被操作,也可以通过代理来操作。
创建代理
代理是使用 Proxy 构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。缺 少其中任何一个参数都会抛出 TypeError。
默认情况下,在代理对象上执行的所有操作都会无障碍地传播到目标对象。
const target = { // ! 目标对象
id: 'target'
}
const handler = {} // ! 处理程序对象
const proxy = new Proxy(target, handler)
// id 属性会访问同一个值
console.log(target.id); // => target
console.log(proxy.id); // => target
// 给目标属性赋值会反映在两个对象上
// 因为两个对象访问的是同一个值
target.id = 'foo';
console.log(target.id); // => foo
console.log(proxy.id); // => foo
// 给代理属性赋值会反映在两个对象上
// 因为这个赋值会转移到目标对象
proxy.id = 'bar';
console.log(target.id); // => bar
console.log(proxy.id); // => bar
console.log(target === proxy); // false
target
:目标对象handler
:处理程序对象
定义捕获器
使用代理的主要目的就是定义捕获器,主要在处理程序对象中创建“拦截器”。
在操作系统中,捕获器是程序流中的一个同步中断,可以暂停程序流,转而执行一段子例程,之后再返回原始程序流。
定义一个 get()捕获器
const target = { // 目标对象
id: 'target'
}
const handler = { // 处理程序对象
get() {
return target change!!!
}
}
const proxy = new Proxy(target, handler)
console.log(target.id) // => target
console.log(proxy.id) // => target change!!!
console.log(target['foo']); // => target
console.log(proxy['foo']); // => target change!!!
console.log(Object.create(target)['foo']); // => target
console.log(Object.create(proxy)['foo']); // => target change!!!
捕获器在处理程序对象中以方法名为键
1.当通过代理对象执行 get()操作时,就会触发定义的 get()捕获器。
2.proxy[property]、proxy.property 或 Object.create(proxy)[property]等操作都 会触发基本的 get()操作以获取属性。
3.只要这些操作作用于代理对象上,就会触发 get()捕获,在目标对象上仍然是正常的行为
捕获器参数
get() 捕获器会接收到目标对象、要查询的属性和代理对象三个参数,于这些参数可以重建被捕获方法的原始行为。
const target = {
id: 'target'
}
const handler = {
get(IsTarget, property, receiver ) {
console.log(IsTarget === target) // => true
console.log(property) // => id
console.log(receiver=== proxy) // => true
/**
* 有了这些参数,我们可以重新处理捕获行为
*/
return IsTarget[property]
}
}
const proxy = new Proxy(target, handler)
console.log(proxy.id) // => target
//执行结果
IsTarget
:目标对象property
: 属性receiver
:代理对象
反射(Reflect)API 方法
捕获器都可以基于自己的参数重建原始操作,实际上、 开发者并不需要手动重建元素行为,而是可以通过调用全局 Reflect 对象(封装了原始行为)的同名方法重建。
const target = {
id: 'target'
}
const handler = {
get: Reflect.get
};
const proxy = new Proxy(target, handler)
console.log(target.id) // => target
console.log(proxy.id) // => target
捕获器不变式
根据 ECMAScript 规范,每个 捕获的方法都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵循“捕获器不变式” (trap invariant)。捕获器不变式因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为。
如果目标对象有一个不可配置不可写的数据属性,那么捕获器会抛出TypeErroe
可撤销代理
Proxy.revocable()
创建一个可撤销的Proxy
对象。
作用:中断代理对象和目标对象之间的联系。(new Proxy() 创建的普通代理会在代理对象的什么周期一直持续存在)
const target = {
id: 'target'
}
const handler = {
get(IsTarget, property, receiver ) {
return 'handler!!!'
}
// get: Reflect.get
}
// ! Proxy.revocable() 可撤销代理对象与目标对象的关联。
const { proxy, revoke } = Proxy.revocable(target, handler)
console.log(target.id) // => target
console.log(proxy.id) // => handler!!!
revoke() // ! 撤销代理
console.log(proxy.id) // => TypeError
这种操作是不可逆的
用一个代理去代理另一个代理
target = {
id: 'target'
}
const handler = {
get(IsTarget, property, receiver ) {
console.log('firstProxy')
return Reflect.get(...arguments)
}
}
const firstProxy = new Proxy(target, handler)
const endProxy = new Proxy(firstProxy, {
get(IsTarget, property, receiver ) {
console.log('endProxy')
return Reflect.get(...arguments)
}
})
console.log(endProxy.id)
// endProxy
// firstProxy
// target
get()
get()
捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为 Reflect.get()。get()
返回值无限制,target
: 目标对象property
:目标对象的字符串键值receiver
:代理对象或者继承代理对象的对象
set()
set()捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set()。
const target = {}
const proxy = new Proxy(target, {
set(target, property, value, receiver) {
console.log('set()')
return Reflect.set(...arguments)
}
})
console.log(proxy.name = '张三')
// set()
// 张三
target
:目标对象。property
:引用的目标对象上的字符串键属性。value
:新属性值。receiver
:接收最初赋值的对象。返回值:返回 true 表示成功;返回 false 表示失败,严格模式下会抛出 TypeError
如果 target.property 不可写且不可配置,则不能修改目标属性的值
has()
handler.has() 方法是针对 in
操作符的代理方法。
in:
如果指定的属性在指定的对象或其原型链中,则in
运算符返回true
。
const target = {}
const proxy = new Proxy(target, {
has(target, prop) {
console.log('has()')
return Reflect.has(...arguments)
}
})
console.log('name' in proxy) // false
target
:目标对象.prop
:需要检查是否存在的属性.has
has()必须返回布尔值,表示属性是否存在。
apply()
handler.apply() 方法用于拦截函数的调用。
function sum(a, b) {
return a + b;
}
const handler = {
apply: function(target, thisArg, argumentsList) {
console.log(`apply ${argumentsList}`);
return target(argumentsList[0], argumentsList[1]) * 10;
}
};
const proxy = new Proxy(sum, handler);
console.log(sum(1, 2)); // => 3
console.log(proxy(1, 2)); // => 30
target
:目标对象(函数)。thisArg
:被调用时的上下文对象。argumentsList
:被调用时的参数数组。返回值:apply方法可以返回任何值。