阅读 144

Proxy及Reflect

创建代理对象

代理是使用 Proxy 构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。缺 少其中任何一个参数都会抛出 TypeError。要创建空代理,可以传一个简单的对象字面量作为处理程序对象,从而让所有操作畅通无阻地抵达目标对象。

const target = {
 id: 'target'
};
const handler = {};
const proxy = new Proxy(target, handler);复制代码

定义捕获器(概念来自操作系统)

使用代理的主要目的是可以定义捕获器(trap)。捕获器就是在处理程序对象中定义的“基本操作的 拦截器”。每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接 或间接在代理对象上调用。每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对 象之前先调用捕获器函数,从而拦截并修改相应的行为。类似之前的Object.defineProperty()

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
console.log(Object.create(target)['foo']); // bar
console.log(Object.create(proxy)['foo']); // handler override 
复制代码

捕获器参数和Reflect API 

所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。比如,get() 捕获器会接收到目标对象、要查询的属性和代理对象三个参数。有了这些参数,就可以重建被捕获方法的原始行为

const target = {
 foo: 'bar'
};
const handler = {
 get(trapTarget, property, receiver) {
 return trapTarget[property];
 }
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar 
复制代码

所有捕获器都可以基于自己的参数重建原始操作,但并非所有捕获器行为都像 get()那么简单。因 此,通过手动写码如法炮制的想法是不现实的。实际上,开发者并不需要手动重建原始行为,而是可以 通过调用全局 Reflect 对象上(封装了原始行为)的同名方法来轻松重建。 处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API 方法。这些方法与捕获器拦截 的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为。

const target = {
 foo: 'bar'
};
const handler = {
 get() {
 return Reflect.get(...arguments);
 }
};
//  可以更简洁
// const handler = {
//   get: Reflect.get
// }; 

const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar 
复制代码

事实上,如果真想创建一个可以捕获所有方法,然后将每个方法转发给对应反射 API 的空代理,那么甚至不需要定义处理程序对象:

const target = {
 foo: 'bar'
};
const proxy = new Proxy(target, Reflect);
console.log(proxy.foo); // bar
console.log(target.foo); // bar 
复制代码

反射 API 与对象 API 

在使用反射 API 时,要记住: (1) 反射 API 并不限于捕获处理程序; (2) 大多数反射 API 方法在 Object 类型上有对应的方法。 通常,Object 上的方法适用于通用程序,而反射方法适用于细粒度的对象控制与操作。

很多反射方法返回称作“状态标记”的布尔值,表示意图执行的操作是否成功。

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

 Reflect.defineProperty() 

 Reflect.preventExtensions() 

 Reflect.setPrototypeOf() 

 Reflect.set() 

 Reflect.deleteProperty()

用一等函数替代操作符 以下反射方法提供只有通过操作符才能完成的操作。 

 Reflect.get():可以替代对象属性访问操作符。 

 Reflect.set():可以替代=赋值操作符。

 Reflect.has():可以替代 in 操作符或 with()。 

 Reflect.deleteProperty():可以替代 delete 操作符。 

 Reflect.construct():可以替代 new 操作符。

代理模式

使用代理可以在代码中实现一些有用的编程模式

跟踪属性访问

 通过捕获 get、set 和 has 等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获 器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过: 

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; // Getting name
proxy.age = 27; // Setting age=27复制代码

隐藏属性

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);
 }
 }
});
// get()
console.log(proxy.foo); // undefined
console.log(proxy.bar); // undefined
console.log(proxy.baz); // 3
// has()
console.log('foo' in proxy); // false
console.log('bar' in proxy); // false
console.log('baz' in proxy); // true 
复制代码

属性验证

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); // 1
proxy.onlyNumbersGoHere = '2';
console.log(proxy.onlyNumbersGoHere); // 1 
复制代码

函数与构造函数参数验证

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)); // 4
console.log(proxy(4, '7', 1));
// Error: Non-number argument provided 
复制代码

数据绑定与可观察对象

通过代理可以把运行时中原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的 代码互操作。 比如,可以将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中:

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); // [User {}, User {}, User{}] 
复制代码

另外,还可以把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息:

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');
// John
proxy.push('Jacob');
// Jacob


作者:用户4932111625461
链接:https://juejin.cn/post/7015883052349931533


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐