概念
- 代理与反射 Proxy | Reflect 是 ES6 新增的
代理的概念: 可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对
目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。
创建 Proxy
代理是使用Proxy 构造函数创建的, 接收两个参数
- 目标对象
- 处理程序对象 ```javascript const target = { id: ‘target’ };
const target = {};
const proxy = new Proxy(target, target);
// 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
// Proxy.prototype 是undefined // 因此不能使用instanceof 操作符 console.log(target instanceof Proxy); // TypeError: Function has non-object prototype console.log(proxy instanceof Proxy); // TypeError: Function has non-object prototype
// 严格相等可以用来区分代理和目标 console.log(target === proxy); // false
Proxy 的特性
- 实例 和 代理对象 属性会访问同一个值
- 给目标属性赋值会反映在两个对象上, 因为两个对象访问的是同一个值
- 给代理属性赋值会反映在两个对象上 , 因为这个赋值会转移到目标对象
- Proxy.prototype 是undefined , 因此不能使用instanceof 操作符
- 严格相等可以用来区分代理和目标 `console.log(target === proxy); // false`
> 书中提到捕获器 , 本质就是 能够在代理时候 对相应的数据进行捕获 ,然后进行操作, 比如 get | set
```javascript
const target = {
foo: 'bar'
};
const handler = {
// 捕获器在处理程序对象中以方法名为键
get() {
return 'handler override';
}
};
const proxy = new Proxy(target, handler);
// 触发 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
Reflect 反射
所有捕获器都可以访问相应的参数, 有了参数, 才可以重建被捕获方法的原始行为。
比如 get() 捕获器会接收的参数
- target 目标对象
- key 要查询的属性
- receiver 代理对象 ```javascript 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, 可以基于自己的参数重建原始操作。 <br />处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API 方法, 这些方法与捕获器拦截<br />的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为。
- Reflect.get()
```javascript
const target = {
foo: 'bar'
};
const handler = {
get() {
return Reflect.get(...arguments)
}
// 或者 简化
get: Reflect.get
};
const proxy = new Proxy(target, handler);
proxy.foo;
撤销代理
有时候可能需要中断代理对象与目标对象之间的联系。
Proxy 也暴露了revocable()方法,这个方法支持撤销代理对象与目标对象的关联。 撤销代理的操作是不可逆的。
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
代理另一个代理
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 的指向
- 代理 与内部槽位
const target = new Date();
const proxy = new Proxy(target, {});
console.log(proxy instanceof Date); // true
proxy.getDate(); // TypeError: 'this' is not a Date object
代理捕获器 get() 与反射方法 set()
get()
get()捕获器会在获取属性值的操作中被调用。对应的反射API 方法为Reflect.get()。
const myTarget = {};
const proxy = new Proxy(myTarget, {
get(target, property, receiver) {
console.log('get()');
return Reflect.get(...arguments)
}
});
proxy.foo;
// get()
set()
set()捕获器会在设置属性值的操作中被调用。对应的反射API 方法为Reflect.set()。
const myTarget = {}
const proxy = new Proxy(myTarget, {
set(target, key, value, receiver) {
console.log('set()');
return Reflect.set(...arguments)
}
})
proxy.foo = 'bar';
// set()
返回值: 返回true 表示成功;返回false 表示失败,严格模式下会抛出TypeError。
has()
用来判断 目标对象,是否存在某个属性
has()捕获器会在in 操作符中被调用。对应的反射API 方法为Reflect.has()。
const myTarget = {};
const proxy = new Proxy(myTarget, {
has(target, property) {
console.log('has()');
return Reflect.has(...arguments)
}
});
'foo' in proxy;
// has()
has()必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。
defineProperty()
defineProperty()捕获器会在Object.defineProperty()中被调用。对应的反射API 方法为
Reflect.defineProperty()。
getOwnPropertyDescriptor()
代理模式
使用代理可以在代码中实现一些有用的编程模式。
跟踪属性访问
通过捕获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