zyzcio.gitee.io
ES6中新增的代理与反射:提供了拦截
并向基本操作嵌入额外行为
的能力;具体的实现方式就是:通过一个给目标对象
定义一个关联的代理对象
,通过代理对象
内部的操作对目标对象
的操作加以控制
注意:代理和反射只在百分百支持它们的平台上有用
从很多方面看,代理
类似C++的指针
,可以用作对象的替身;当然,目标对象既可以直接操作(会绕过代理)、也可以通过代理来操作。
通过Proxy
构造函数创建,其接收两个参数:
两个参数是必需
的;缺一个就会报错。
// 创建目标对象
const targetObject = {
name:'target'
}
// 创建空处理程序对象
const handler = { /* 对数据进行一些操作(捕获器) */};
const proxy = new Proxy(targetObject,handler);
// 给目标对象赋值 会反映在两个对象上
targetObject.name = 'hello';
console.log(targetObject.name); //hello
console.log(proxy.name); //hello
// 給代理对象赋值 也会反映在两个对象上
proxy.name = 'world';
console.log(targetObject.name); //world
console.log(proxy.name); //world
使用代理的主要目的是定义捕获器
;
0个或多个
;基本操作
,可以直接
或间接
在代理对象上调用;每次在代理对象
上调用这些基本操作
的时候,代理
可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。
const targetObject = {
name:'target'
}
const handler = {
get() {
return "hello world!"
}
};
const proxy = new Proxy(targetObject,handler);
console.log(targetObject.name); // target
console.log(proxy.name); // hello world!
显然,直接操作对象,会绕过代理器
所有捕获器都可以访问相应的参数
,可以基于这些参数
重建被捕获方法的原始行为。一般参数如下:
const targetObject = {
name:'target'
}
const handler = {
get(trapTarget,property,receiver) {
console.log(trapTarget); // { name:'target' }
console.log(property); // name
console.log(receiver); // { name:'target' }
return targetObject[name]; // 如果不返回,则标识返回undefined
}
};
const proxy = new Proxy(targetObject,handler);
console.log(targetObject.name); // target
console.log(proxy.name); // target
实际上,并不需要开发者手动重建原始行为,而可以通过
全局Reflect
对象的同名方法来进行重建。const targetObject = { name:'target' } const handler = { get(trapTarget,property,receiver) { return Reflect.get(...arguments); } }; const proxy = new Proxy(targetObject,handler); console.log(targetObject.name); // target console.log(proxy.name); // target
显然,上面只是创建了所有属性
的捕获方法,一般实际情况会如下:
const targetObject = {
id:1,
name:'target',
addr:'天堂'
}
const handler = {
get(trapTarget,property,receiver) {
let decoration = '';
if( trapTarget[property] === '天堂' ){
decoration = "上帝的邻居:";
}
return decoration + Reflect.get(...arguments);
}
};
const proxy = new Proxy(targetObject,handler);
console.log(proxy.name); // target
console.log(proxy.addr); // 上帝的邻居:天堂
个人理解:捕获器也需要遵循一定的规则
当属性
不可配置
或不可写入
的时候,不可进行操作const target = {} Object.defineProperty(target, 'sex' , { configurable: false, writable:false, value:'male' }) const handler = { get(){ return 'female' } } const proxy = new Proxy(target,handler); console.log(proxy.sex); // 报错TypeError
除了使用Proxy
的构造函数创建代理之外,Proxy
还暴露了revocable()
和revoke()
分别用于关联和撤销代理。
const target = {
id:1,
name:'zyzc',
title:'hello'
}
const handler = {
get(trapObject,property,receiver) {
return Reflect.get(...arguments);
}
}
const { proxy , revoke } = Proxy.revocable(target,handler);
console.log(proxy.name); // zyzc
revoke();
console.log(proxy.name) // Cannot perform 'get' on a proxy that has been revoked
在某些情况下,应该优先使用反射API:
反射API 与 对象API对比
状态标记
很多反射方法会返回称作状态标记
的布尔值,表示意图执行的操作是否成功。这些方法有:
Reflect.defineProperty()
Reflect.preventExtensions()
Reflect.setPrototypeOf()
Reflect.set()
Reflect.deleteProperty()
使用反射API,可以重构那些:返回修改后对象、抛出错误的方法。
初始代码:
const object = {}; try { Object.defineProperty(object,'name','zyzc'); console.log('success'); }catch(e){ console.log('failure'); }
反射API重构之后:
const object = {}; if(Reflect.defineProperty(object,'name','zyzc')){ console.log('success'); }else{ console.log('failure'); }
一等函数代替操作符
Reflect.get()
:代替属性访问操作符Reflect.set()
:代替赋值操作符Reflect.has()
:代替in或with()Reflect.deleteProperty()
:代替deleteReflect.construct()
:代替new安全地应用函数
在几乎不可能的情况下,当方法名apply和属性名apply一致的时候,可以使用Reflect.apply()
来代替。
可以使用一个代理,代理另一个代理,构建多层拦截网
很大程度上,代理作为对象的虚拟层
可以正常使用。但是有时候并不完美;
代理的this
const target = {
show(){
return this === proxy
}
}
const proxy = new Proxy(target,{})
console.log(target.show()); // false
console.log(proxy.show()); // true
这样没什么问题,如果遇到了依赖的实例对象标识,就会出现如下问题:
const wm = new WeakMap();
class User {
constructor(userId){
wm.set(this,userId);
}
set id(userId){
wm.set(this,userId);
}
get id(){
return wm.get(this);
}
}
const user = new User(123);
const proxy = new Proxy(user,{});
console.log(user.id); // 123;
console.log(proxy.id); // undefined
需要这样操作!【个人感觉没什么用…】
const wm = new WeakMap();
class User {
constructor(userId){
wm.set(this,userId);
}
set id(userId){
wm.set(this,userId);
}
get id(){
return wm.get(this);
}
}
const userClassProxy = new Proxy(User,{});
const proxy = new userClassProxy(123);
console.log(proxy.id); // 123
代理与内部槽位
代理与内置引用类型的实例,一般可以协同,但是有一些内置类型可能会依赖代理无法控制的机制
。
比如说
Date
:Date
类型方法的执行,依赖this
值上的内部槽位[[NumberDate]]
,但是代理对象
并不存在这个槽位,而且内部槽位的值不能通过普通的get()
和set()
操作。所以当代理拦截后,本应转发给目标对象的方法,会抛出TypeError
介绍前几种,其他的类似就不一一叙述
get()
捕获器在获取属性值的操作中被调用。
- 返回值
- 没有返回值限制
- 拦截的操作
proxy.property
proxy[property]
Object.create(proxy)[property]
Reflect.get(proxy, property, receiver)
- 捕获器处理程序参数
- target:目标对象
- property:引用的目标对象上的字符串键属性。
- receiver:代理对象或继承代理对象的对象
- 捕获器不变式
- 如果
target.property
不可写且不可配置, 则处理程序返回的值必须与target.property
匹配。- 如果
target.property
不可配置且[[Get]]
特性为undefined
, 处理程序的返回值也必须是undefined
。
set()
捕获器设置属性值的操作中被调用。
- 返回值
- true
- false【严格模式下抛出错误】
- 拦截的操作
proxy.property = value
proxy[property] = value
Object.create(proxy)[property] = value
Reflect.set(proxy, property, value, receiver)
- 捕获器处理程序参数
- target: 目标对象。
- property: 引用的目标对象上的字符串键属性。
- value: 要赋给属性的值。
- receiver: 接收最初赋值的对象。
- 捕获器不定式
- 如果
target.property
不可写且不可配置, 则不能修改目标属性的值。- 如果
target.property
不可配置且[[Set]]
特性为undefined
, 则不能修改目标属性的值。- 在严格模式下, 处理程序中返回false会抛出
TypeError
has()
捕获器会在in
操作符中被调用。
- 返回值【表示是否存在】
- true
- false
- 拦截的操作
property in proxy
property in Object.create(proxy)
with(proxy) {(property);}
Reflect.has(proxy, property)
- 捕获器处理程序参数
- target: 目标对象。
- property: 引用的目标对象上的字符串键属性。
- 捕获器不变式
- 如果
target.property
存在且不可配置, 则处理程序必须返回true
。- 如果
target.property
存在且目标对象不可扩展, 则处理程序必须返回true
。
defineProperty()
捕获器会在Object.defineProperty()
中被调用。
- 返回值【表示是否定义成功】
- true
- false
- 拦截的操作
Object.defineProperty(proxy, property, descriptor)
Reflect.defineProperty(proxy, property, descriptor)
- 捕获器处理程序参数
- target: 目标对象。
- property: 引用的目标对象上的字符串键属性。
- descriptor: 包含可选的enumerable、 configurable、 writable、 value、 get和set定义的对象
- 捕获器不变式
- 如果目标对象不可扩展, 则无法定义属性
- 如果目标对象有一个可配置的属性, 则不能添加同名的不可配置属性
- 如果目标对象有一个不可配置的属性, 则不能添加同名的可配置属性
在函数被调用的时候捕获
- 返回值
- 返回值无限制
- 拦截的操作
proxy(...argumentsList)
Function.prototype.apply(thisArg, argumentsList)
Function.prototype.call(thisArg, ...argumentsList)
Reflect.apply(target, thisArgument, argumentsList)
- 捕获器处理程序参数
- target: 目标对象。
- thisArg: 调用函数时的this参数。
- argumentsList: 调用函数时的参数列表
- 捕获器不变式
- target必需是一个对象
可以利用代理,实现一些
编程模式
可以通过get、set和has
操作,知道属性对象什么时候被访问、被查询;
const user = {
name:'zyzc'
}
const proxy = new Proxy(user, {
get(target,property,receiver){
console.log(`${target} is Getting ${property}`)
return Reflect.get(...arguments);
},
set(target,property,value,receiver){
console.log(`${target} is Setting ${property} to ${value}`);
return Reflect.set(...arguments);
}
})
proxy.name; // [object Object] is Getting name
proxy.age = 21; // [object Object] is Setting age to 21
使得代码
对外部不可见
const privateProperty = ['age','sex','addr']
const user = {
name:'zyzc',
age:21,
}
// 将age设为不可见
const proxy = new Proxy(user,{
get(target,property,receiver) {
if(privateProperty.includes(property)){
return undefined;
}else {
return Reflect.get(...arguments);
}
},
has(target,property,receiver) {
if(privateProperty.includes(property)){
return false;
}else{
return Reflect.has(...arguments);
}
}
})
console.log(proxy.name); // zyzc
console.log(proxy.age); // undefined
console.log('age' in proxy);// false
使得设置属性的时候,判断是否允许赋值
const target = {
name:'zyzc',
age:21
}
const proxy = new Proxy(target,{
set(target,property,value,receiver){
if(property === 'age') {
if(typeof value === 'number' ){
return Reflect.set(...arguments);
}else{
return false;
}
}
}
})
proxy.age = 'haha';
console.log(proxy.age); // 21
proxy.age = 22;
console.log(proxy.age); // 22
function getMedian(...nums){
return nums.sort()[Math.floor(nums.length / 2)];
}
const proxy = new Proxy(getMedian, {
apply(target,thisArg,argumentsList) {
for(const arg of argumentsList) {
if(typeof arg !== 'number'){
throw 'You should pass a Number';
}
}
return Reflect.apply(...arguments);
}
})
console.log(proxy(9,8,7,1,2,3)); // 7
console.log(proxy("haha",1,2,3,4)); // You should pass a Number
可以将被代理的类,绑定到一个全局实例集合中
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 User('zyzc1'); new User('zyzc2'); console.log(userList); // [ User { _name: 'zyzc1' }, User { _name: 'zyzc2' } ]
还可以给集合绑定一个监听事件
const userList = []; // 具有内部属性 index、length,所以每次set的时候,都会遍历自定义属性和内部属性 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('zyzc1'); // 你插入了:1 你插入了:zyzc1 proxy.push('zyzc2'); // 你插入了:2 你插入了:zyzc2 console.log(proxy) // [ 'zyzc1', 'zyzc2' ]
为什么会多了一个1和2呢?我认为应该是在
if
里面,获取目标对象的属性时,会遍历其内部属性,如index或者length之类的,导致多出了1和2const userList = []; function emit(newValue) { console.log("你插入了:"+newValue) } const proxy = new Proxy(userList , { set(target,property,value,receiver){ const result = Reflect.set(...arguments); if(result) { let temp = Reflect.get(target,property,receiver); console.log("获取的目标对象是:"+target); // ['zyzc1'] ['zyzc1'] ['zyzc1','zyzc2'] ['zyzc1','zyzc2'] console.log("获取的目标属性是:"+property); // 0 length 1 length //emit(); } return result; }, }); proxy.push('zyzc1'); // 你插入了:1 你插入了:zyzc1 proxy.push('zyzc2'); // 你插入了:2 你插入了:zyzc2 console.log(proxy);
目前还没有想出有什么好的解释;
元编程
及抽象
的新区域