• 代理类似C++指针,因为它可以用作目标对象的替身,但又完全独立于目标对象;
  • 目标对象既可以直接被操作,也可以通过代理来操作。
  • 默认情况下,在代理对象上执行的所有操作都会无障碍地传播到目标对象。
  • 在任何可以使用目标对象的地方,都可以通过同样的方式来使用与之关联的代理对象
  • 直接操作会绕过代理施予的行为。

空代理

  • 除了作为一个抽象的目标对象,什么也不做
  • 代理是使用Proxy构造函数创建的,构造函数接收两个参数:目标对象和处理程序对象
    • 缺少其中任何一个参数都会抛出TypeError;
    • 创建空代理,可以传一个简单的对象字面量作为处理程序对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
// hasOwnProperty()方法在两个地方
// 都会应用到目标对象
console.log(target.hasOwnProperty('id')); // true
console.log(proxy.hasOwnProperty('id')); // true
// Proxy.prototype 是 undefined
// 因此不能使用 instanceof 操作符
console.log(target instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check
console.log(proxy instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check
// 严格相等可以用来区分代理和目标
console.log(target === proxy); // false

定义捕获器

  • 捕获器就是在处理程序对象中定义的“基本操作的拦截器”
  • 每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接或间接在代理对象上调用。
  • 每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。
  • 只有在代理对象上执行操作才会触发捕获器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const target = { 
foo: 'bar'
};
const handler = {
// 捕获器在处理程序对象中以方法名为键
get() {
return 'handler override';
}
};
const proxy = new Proxy(target, handler);
//proxy[property] 、proxy.property或Object.create(proxy)[property]等操作都会触发基本的get()操作以获取属性。
console.log(target.foo); // bar
console.log(proxy.foo); // handler override
console.log(target['foo']); // bar
console.log(proxy['foo']); // handler override
console.log(Object.create(target)['foo']); // bar
console.log(Object.create(proxy)['foo']); // handler override

捕获器参数和反射API

  • 所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const target = { 
foo: 'bar'
};

const handler = {
get(trapTarget, property, receiver) {
console.log(trapTarget === target);
console.log(property);
console.log(receiver === proxy);
}
};
const proxy = new Proxy(target, handler);
proxy.foo;
// true
// foo
// true
  • 所有捕获器都可以基于自己的参数重建原始操作。
  • 通过调用全局Reflect对象上(封装了原始行为)的同名方法来轻松重建原始操作。
1
2
3
4
5
6
7
8
9
10
11
const target = { 
foo: 'bar'
};
const handler = {
get() {
return Reflect.get(...arguments);
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar
  • 事实上,如果真想创建一个可以捕获所有方法,然后将每个方法转发给对应反射API的突代理那么甚至不需要定义处理程序对象:
1
2
3
4
5
6
const target = { 
foo: 'bar'
};
const proxy = new Proxy(target, Reflect);
console.log(proxy.foo); // bar
console.log(target.foo); // bar

捕获器不变式

  • 每个捕获的方法都知道目标对象上下文、捕获函数签名。
  • 捕获处理程序的行为必须遵循“捕获器不变式”。
  • 捕获器不变式因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为
  • 如果目标对象有一个不可配置且不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,会抛出TypeError:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const target = {}; 
Object.defineProperty(target, 'foo', {
configurable: false,
writable: false,
value: 'bar'
});
const handler = {
get() {
return 'qux';
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo);
// TypeError

可撤销代理

  • 使用new Proxy()创建的普通代理,这种联系会在代理对象的生命周期内一直持续存在。
  • Proxy的revocable()方法,这个方法支持撇销代理对象与目标对象的关联。
    • 撤销代理的操作是不可逆的
    • 撤销函数(revoke())是幂等的,调用多少次的结果都一样;
    • 撤销代理之后再调用代理会抛出TypeError。
    • 撤销函数和代理对象是在实例化时同时生成的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const target = { 
foo: 'bar'
};
const handler = {
get() {
return 'intercepted';
}
};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.foo); // intercepted
console.log(target.foo); // bar
//撤销代理
revoke();
console.log(proxy.foo); // TypeError

实用反射API

反射API与对象API

  • 反射API并不限于捕获处理程序。
  • 大多数反射API方法在Object类型上有对应的方法。
  • Object上的方法适用于通用程序,而反射方法适用于细粒度的对象控制与操作。

状态标记

  • 状态标记是指:反射方法返回的布尔值;表示意图执行的操作是否成功。

以下反射方法会提供状态标记:

  • Reflect.defineProperty ()
  • Reflect.preventExtensions ()
  • Reflect.setPrototypeof ()
  • Reflect.set ()
  • Reflect.deleteProperty ()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 初始代码 
const o = {};
try {
Object.defineProperty(o, 'foo', 'bar');
console.log('success');
} catch(e) {
console.log('failure');
}
// 重构后的代码
const o = {};
if(Reflect.defineProperty(o, 'foo', {value: 'bar'})) {
console.log('success');
} else {
console.log('failure');
}

用一等函数代替操作符

安全地应用函数

  • 通过apply方法调用函数时,被调用的函数可能也定义了自己的apply属性,为绕过这个问题我们可以采取以下两种方法来避免:
    • Function.prototype.apply.call(myFunc, thisVal, argumentList);
    • Reflect.apply(myFunc, thisVal, argumentsList);

代理另一个代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const target = { 
foo: 'bar'
};
const firstProxy = new Proxy(target, {
get() {
console.log('first proxy');
return Reflect.get(...arguments);
}
});
const secondProxy = new Proxy(firstProxy, {
get() {
console.log('second proxy');
return Reflect.get(...arguments);
}
});
console.log(secondProxy.foo);
// second proxy
// first proxy
// bar

代理存在的问题与不足

  • 代理中的this;
  • 代理与内部槽位;