JavaScript高级程序设计(4)
第9章 代理与反射
### 9.1 代理基础 266 ##### 9.1.1创建空代理 > 代理是使用 Proxy 构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。缺少其中任何一个参数都会抛出 TypeError。要创建空代理,可以传一个简单的对象字面量作为处理程序对象,从而让所有操作畅通无阻地抵达目标对象。 ```javascript 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 ``` ##### 9.1.2 定义捕获器 >使用代理的主要目的是可以定义捕获器(trap)。捕获器就是在处理程序对象中定义的“基本操作的拦截器”。每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。 ```javascript const target = { foo: 'bar' }; const handler = { // 捕获器在处理程序对象中以方法名为键 get() { return 'handler override'; } }; const proxy = new Proxy(target, handler); console.log(target.foo); // bar console.log(proxy.foo); // handler override console.log(target['foo']); // bar console.log(proxy['foo']); // handler override ``` ##### 9.1.3 捕获器参数和反射API ##### 9.1.4 捕获器不变式 > 使用捕获器几乎可以改变所有基本方法的行为,但也不是没有限制。根据 ECMAScript 规范,每个捕获的方法都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵循“捕获器不变式”(trap invariant)。捕获器不变式因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为。 ##### 9.1.5 可撤销代理 ```javascript //撤销函数和代理对象是在实例化时同时生成的: const target = { foo: 'bar' }; const handler = { get() { return 'intercepted'; } }; const { proxy, revoke } = Proxy.revocable(target, handler); revoke(); //撤销代理 console.log(proxy.foo); // TypeError ``` ##### 9.1.6 实用反射API ##### 9.1.7 代理另一个代理 > 代理可以拦截反射 API 的操作,而这意味着完全可以创建一个代理,通过它去代理另一个代理。这样就可以在一个目标对象之上构建多层拦截网: ```javascript 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 ``` ##### 9.1.8 代理的问题与不足 - 代理中的 **this** - 代理与内部槽位 ### 9.2 代理捕获器与反射方法 274 ##### 9.2.1 get() > get()捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为 Reflect.get()。 ```javascript const myTarget = {}; const proxy = new Proxy(myTarget, { get(target, property, receiver) { console.log('get()'); return Reflect.get(...arguments) ; } }); ``` ##### 9.2.2 set() > set()捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set()。 > > 返回 true 表示成功;返回 false 表示失败,严格模式下会抛出 TypeError。 ##### 9.2.3 has() > has()捕获器会在 in 操作符中被调用。对应的反射 API 方法为 Reflect.has()。 > > 必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。 ##### 9.2.4 defineProperty() >会在 Object.defineProperty()中被调用。对应的反射 API 方法为Reflect.defineProperty()。 > >必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值。 ##### 9.2.5 getOwnPropertyDescriptor() >会在 Object.getOwnPropertyDescriptor()中被调用。对应的反射 API 方法为 Reflect.getOwnPropertyDescriptor()。 > >必须返回对象,或者在属性不存在时返回 undefined ##### 9.2.6 deleteProperty() >会在 delete 操作符中被调用。对应的反射 API 方法为 Reflect. deleteProperty()。 > >必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。 ##### 9.2.7 ownKeys() >会在 Object.keys()及类似方法中被调用。对应的反射 API 方法为 Reflect. ownKeys()。 > >必须返回包含字符串或符号的可枚举对象。 ##### 9.2.8 getPrototypeOf() >会在 Object.getPrototypeOf()中被调用。对应的反射 API 方法为Reflect.getPrototypeOf()。 > >必须返回对象或 null。 ##### 9.2.9 setPrototypeOf() >会在 Object.setPrototypeOf()中被调用。对应的反射 API 方法为Reflect.setPrototypeOf()。 > >必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值。 ##### 9.2.10 isExtensible() >会在 Object.isExtensible()中被调用。对应的反射 API 方法为Reflect.isExtensible()。 > >isExtensible()必须返回布尔值,表示 target 是否可扩展。返回非布尔值会被转型为布尔值。 ##### 9.2.11 preventExtensions() >会在 Object.preventExtensions()中被调用。对应的反射 API方法为 Reflect.preventExtensions()。 > >必须返回布尔值,表示 target 是否已经不可扩展。返回非布尔值会被转型为布尔值。 ##### 9.2.12 apply() > 会在调用函数时中被调用。对应的反射 API 方法为 Reflect.apply() > > 返回值无限制。 ##### 9.2.13 construct() > 会在 new 操作符中被调用。对应的反射 API 方法为 Reflect.construct()。 > > 必须返回一个对象。 ### 9.3 代理模式 283 ##### 9.3.1 跟踪属性访问 > 通过捕获 get、set 和 has 等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过: ```javascript 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); } }); ``` ##### 9.3.2 隐藏属性 > 代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。 ```javascript 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); } } }); ``` ##### 9.3.3 届性验证 > 因为所有赋值操作都会触发 set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值: ```javascript const target = { onlyNumbersGoHere: 0 }; const proxy = new Proxy(target, { set(target, property, value) { if (typeof value !== 'number') { return false; } else { return Reflect.set(...arguments); } } }); ``` ##### 9.3.4 函数与构造函数参数验证 >跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查。比如,可以让函数只接收某种类型的值: ```javascript 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); } }); ``` ##### 9.3.5 数据绑定与可观察对急 > 通过代理可以把运行时中原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的代码互操作。比如,可以将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中。另外,还可以把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息 ```javascript 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; } }); ```
顶部
收展
底部
[TOC]
目录
第1章 JavaScript简介
第2章 在 HTML中使用JavaScript
第3章 语言基础(1)语法变量
第3章 语言基础(2)数据类型
第3章 语言基础(3)操作符
第3章 语言基础(4)语句
第4章 变量、作用域与内存
第5章 基本引用类型
第6章 集合引用类型
第7章 迭代器与生成器
第8 章对象、类与面向对象编程
第9章 代理与反射
第10章 函数
第11章 期约与异步函数
第12章 BOM
第13章 客户端检测
第14章 DOM
第15章 DOM 扩展
第16章 DOM2 和 DOM3
第17章 事件
第18章 动画与 Canvas 图形
第19章 表单脚本
第20章 JavaScript API
第21章 错误处理与调试
第22章 处理 XML
第23章 JSON
第24章 网络请求与远程资源
第25章 客户端存储
第26章 模块
第27章 工作者线程
第28章 最佳实践
相关推荐
WebSocket