容言,则日有所进;容事,则事无不成;容人,则人无不和。宽容待人,也会被世界温柔相待。
今日学习内容
1、JS 红皮书 P283-286 第九章:代理与反射
今日笔记
1、代理模式:
- 跟踪属性访问: 通过捕获 get、set 和 has 等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const user = { name: "Jake", }; const proxy = new Proxy(user, { get(target, property, receiver) { console.log(`Getting ${property}`); return Reflect.get(...arguments); }, set(target, property, value, receiver) { console.log(`Setting ${property}=${value}`); return Reflect.set(...arguments); }, }); proxy.name; proxy.age = 27;
|
- 隐藏属性: 代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。比如:
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 30
| const hiddenProperties = ["foo", "bar"]; const targetObject = { foo: 1, bar: 2, baz: 3, }; const proxy = new Proxy(targetObject, { get(target, property) { if (hiddenProperties.includes(property)) { return undefined; } else { return Reflect.get(...arguments); } }, has(target, property) { if (hiddenProperties.includes(property)) { return false; } else { return Reflect.has(...arguments); } }, });
console.log(proxy.foo); console.log(proxy.bar); console.log(proxy.baz);
console.log("foo" in proxy); console.log("bar" in proxy); console.log("baz" in proxy);
|
- 属性验证: 因为所有赋值操作都会触发 set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const target = { onlyNumbersGoHere: 0, }; const proxy = new Proxy(target, { set(target, property, value) { if (typeof value !== "number") { return false; } else { return Reflect.set(...arguments); } }, }); proxy.onlyNumbersGoHere = 1; console.log(proxy.onlyNumbersGoHere); proxy.onlyNumbersGoHere = "2"; console.log(proxy.onlyNumbersGoHere);
|
- 函数与构造函数参数验证: 跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查。比如,可以让函数只接收某种类型的值:
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 30 31 32 33 34
| function median(...nums) { return nums.sort()[Math.floor(nums.length / 2)]; } const proxy = new Proxy(median, { apply(target, thisArg, argumentsList) { for (const arg of argumentsList) { if (typeof arg !== "number") { throw "Non-number argument provided"; } } return Reflect.apply(...arguments); }, }); console.log(proxy(4, 7, 1)); console.log(proxy(4, "7", 1));
class User { constructor(id) { this.id_ = id; } } const proxy = new Proxy(User, { construct(target, argumentsList, newTarget) { if (argumentsList[0] === undefined) { throw "User cannot be instantiated without id"; } else { return Reflect.construct(...arguments); } }, }); new proxy(1); new proxy();
|
2、数据绑定与可观察对象: 通过代理可以把运行时中原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的代码互操作。
比如,可以将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const userList = []; class User { constructor(name) { this.name_ = name; } } const proxy = new Proxy(User, { construct() { const newUser = Reflect.construct(...arguments); userList.push(newUser); return newUser; }, }); new proxy("John"); new proxy("Jacob"); new proxy("Jingleheimerschmidt"); console.log(userList);
|
另外,还可以把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const userList = []; function emit(newValue) { console.log(newValue); } const proxy = new Proxy(userList, { set(target, property, value, receiver) { const result = Reflect.set(...arguments); if (result) { emit(Reflect.get(target, property, receiver)); } return result; }, }); proxy.push("John");
proxy.push("Jacob");
|
3、小结:
代理是 ECMAScript 6 新增的令人兴奋和动态十足的新特性。尽管不支持向后兼容,但它开辟出了一片前所未有的 JavaScript 元编程及抽象的新天地。
从宏观上看,代理是真实 JavaScript 对象的透明抽象层。代理可以定义包含捕获器的处理程序对象,而这些捕获器可以拦截绝大部分 JavaScript 的基本操作和方法。在这个捕获器处理程序中,可以修改任何基本操作的行为,当然前提是遵从捕获器不变式。
与代理如影随形的反射 API,则封装了一整套与捕获器拦截的操作相对应的方法。可以把反射 API看作一套基本操作,这些操作是绝大部分 JavaScript 对象 API 的基础。
代理的应用场景是不可限量的。开发者使用它可以创建出各种编码模式,比如(但远远不限于)跟踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定,以及可观察对象。