参考文章1:https://www.jb51.net/article/132370.htm
参考文章2:https://www.jianshu.com/p/c2a1aa2e2b14
参考文章3:https://segmentfault.com/a/1190000020221597
Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。
在支持 Proxy 的浏览器环境中,Proxy 是一个全局对象,可以直接使用。Proxy(target, handler) 是一个构造函数,target 是被代理的对象,handlder 是声明了各类代理操作的对象,最终返回一个代理对象。外界每次通过代理对象访问 target 对象的属性时,就会经过 handler 对象,从这个流程来看,代理对象很类似 middleware(中间件)。那么 Proxy 可以拦截什么操作呢?最常见的就是 get(读取)、set(修改)对象属性等操作。此外,Proxy 对象还提供了一个 revoke 方法,可以随时注销所有的代理操作。在我们正式介绍 Proxy 之前,建议你对 Reflect 有一定的了解,它也是一个 ES6 新增的全局对象
1、proxy
一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。
let target = {
name: 'Tom',
age: 24
}
let handler = {
get: function(target, key) {
console.log('getting '+key);
return target[key]; // 不是target.key
},
set: function(target, key, value) {
console.log('setting '+key);
target[key] = value;
}
}
let proxy = new Proxy(target, handler)
proxy.name // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
// getting name
// setting age
// 25
// target 可以为空对象
let targetEpt = {}
let proxyEpt = new Proxy(targetEpt, handler)
// 调用 get 方法,此时目标对象为空,没有 name 属性
proxyEpt.name // getting name
// 调用 set 方法,向目标对象中添加了 name 属性
proxyEpt.name = 'Tom'
// setting name
// "Tom"
// 再次调用 get ,此时已经存在 name 属性
proxyEpt.name
// getting name
// "Tom"
// 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相
// 影响
targetEpt)
// {name: "Tom"}
// handler 对象也可以为空,相当于不设置拦截操作,直接访问目标对象
let targetEmpty = {}
let proxyEmpty = new Proxy(targetEmpty,{})
proxyEmpty.name = "Tom"
targetEmpty) // {name: "Tom"}
使用场景
1. 抽离校验模块
让我们从一个简单的类型校验开始做起,这个示例演示了如何使用 Proxy 保障数据类型的准确性:
let numericDataStore = {
count: 0,
amount: 1234,
total: 14
};
numericDataStore = new Proxy(numericDataStore, {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("Properties in numericDataStore can only be numbers");
}
return Reflect.set(target, key, value, proxy);
}
});
// 抛出错误,因为 "foo" 不是数值
numericDataStore.count = "foo";
// 赋值成功
numericDataStore.count = 333;
如果要直接为对象的所有属性开发一个校验器可能很快就会让代码结构变得臃肿,使用 Proxy 则可以将校验器从核心逻辑分离出来自成一体:
function createValidator(target, validator) {
return new Proxy(target, {
_validator: validator,
set(target, key, value, proxy) {
if (target.hasOwnProperty(key)) {
let validator = this._validator[key];
if (!!validator(value)) {
return Reflect.set(target, key, value, proxy);
} else {
throw Error(`Cannot set ${key} to ${value}. Invalid.`);
}
} else {
throw Error(`${key} is not a valid property`)
}
}
});
}
const personValidators = {
name(val) {
return typeof val === 'string';
},
age(val) {
return typeof age === 'number' && age > 18;
}
}
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
return createValidator(this, personValidators);
}
}
const bill = new Person('Bill', 25);
// 以下操作都会报错
bill.name = 0;
bill.age = 'Bill';
bill.age = 15;
通过校验器和主逻辑的分离,你可以无限扩展 personValidators 校验器的内容,而不会对相关的类或函数造成直接破坏。更复杂一点,我们还可以使用 Proxy 模拟类型检查,检查函数是否接收了类型和数量都正确的参数:
function createValidator(target, validator) {
return new Proxy(target, {
_validator: validator,
set(target, key, value, proxy) {
if (target.hasOwnProperty(key)) {
let validator = this._validator[key];
if (!!validator(value)) {
return Reflect.set(target, key, value, proxy);
} else {
throw Error(`Cannot set ${key} to ${value}. Invalid.`);
}
} else {
throw Error(`${key} is not a valid property`)
}
}
});
}
const personValidators = {
name(val) {
return typeof val === 'string';
},
age(val) {
return typeof age === 'number' && age > 18;
}
}
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
return createValidator(this, personValidators);
}
}
const bill = new Person('Bill', 25);
// 以下操作都会报错
bill.name = 0;
bill.age = 'Bill';
bill.age = 15;
2、过滤操作
某些操作会非常占用资源,比如传输大文件,这个时候如果文件已经在分块发送了,就不需要在对新的请求作出相应(非绝对),这个时候就可以使用
Proxy
对当请求进行特征检测,并根据特征过滤出哪些是不需要响应的,哪些是需要响应的。下面的代码简单演示了过滤特征的方式,并不是完整代码,相信大家会理解其中的妙处
let obj = {
getGiantFile: function(fileId) {/*...*/ }
};
obj = new Proxy(obj, {
get(target, key, proxy) {
return function(...args) {
const id = args[0];
let isEnroute = checkEnroute(id);
let isDownloading = checkStatus(id);
let cached = getCached(id);
if (isEnroute || isDownloading) {
return false;
}
if (cached) {
return cached;
}
return Reflect.apply(target[key], target, args);
}
}
});
3. 中断代理
Proxy
支持随时取消对target
的代理,这一操作常用于完全封闭对数据或接口的访问。在下面的示例中,我们使用了Proxy.revocable
方法创建了可撤销代理的代理对象:
let sensitiveData = { username: 'devbryce' };
const {sensitiveData, revokeAccess} = Proxy.revocable(sensitiveData, handler);
function handleSuspectedHack(){
revokeAccess();
}
// logs 'devbryce'
console.log(sensitiveData.username);
handleSuspectedHack();
// TypeError: Revoked
console.log(sensitiveData.username);
Reflect对象一共有 13 个静态方法。我们依次用老写法和新写法做对比!
1.Reflect.get
获取对象中对应key的值
const my = {
name:'song',
age:18,
get mny(){
return this.a + this.b
}
}
console.log(my['age']); // 老写法
console.log(Reflect.get(my,'age'));
console.log(Reflect.get(my,'mny',{a:1,b:2})); // 可以指定this指向
2.Reflect.set
设置对象中key对应的值
const my = {
name: "song",
age: 18,
set mny(val) {
this.value = val;
}
};
let mny = { value: 0 };
my.mny = 100; // 老写法
Reflect.set(my, "mny", 100);
Reflect.set(my, "mny", 100, mny); // 给对象设置属性,并且传递this
console.log(mny); // {value:100}
3.Reflect.has
判断某个key是否属于这个对象
const my = {
name:'song'
}
console.log('name' in my);
console.log(Reflect.has(my,'name'));
4.Reflect.defineProperty
定义对象的属性和值,等价于Object.defineProperty
const person = {};
Object.defineProperty(person,'name',{
configurable:false,
value:'song'
});
console.log(person.name); // 老写法,后续会被废弃
Reflect.defineProperty(person,'name',{
configurable:false,
value:'song'
})
console.log(person.name);
5.Reflect.deleteProperty
删除对象中某个属性
const person = {};
Reflect.defineProperty(person,'name',{
configurable:false,
value:'song'
});
// delete person.name; 无返回值
const flag = Reflect.deleteProperty(person,'name');
console.log(flag); // 返回是否删除成功
6.Reflect.construct
实例化类,等价于new
class Person{
constructor(sex){
console.log(sex);
}
}
new Person('女'); // 老写法
Reflect.construct(Person,['男']);
7.Reflect.getPrototypeOf
读取proto,等价于Object.getPrototypeOf,不同的是如果方法传递的不是对象会报错!
class Person {}
// 老写法
console.log(Object.getPrototypeOf(Person) === Reflect.getPrototypeOf(Person));
8.Reflect.setPrototypeOf
设置proto,等价于Object.setPrototypeOf,不同的是返回一个boolean类型表示是否设置成功!
let person = {name:'song'};
let obj = {age:18};
// Object.setPrototypeOf(person,obj); // 老写法
Reflect.setPrototypeOf(person,obj);
console.log(person.age);
9.Reflect.apply
想必apply方法大家都很了解了, Reflect.apply 等价于Function.prototype.apply.call
const func = function(a,b){
console.log(this,a,b);
}
func.apply = () =>{
console.log('apply')
}
// func.apply({name:'song'},[1,2]); // 调用的是自己身上的方法
Function.prototype.apply.call(func,{name:'song'},[1,2]); // 老写法
Reflect.apply(func,{name:'song'},[1,2]); // 是不是非常的简单!
10.Reflect.getOwnPropertyDescriptor
等价于Object.getOwnPropertyDescriptor,获取属性描述对象
const obj = {name:1};
// const descriptor = Object.getOwnPropertyDescriptor(obj,'name'); // 老写法
const descriptor = Reflect.getOwnPropertyDescriptor(obj,'name');
console.log(descriptor);
11.Reflect.preventExtensions
让一个对象变的不可扩展,也就是永远不能再添加新的属性,等价于 Object.preventExtensions
const person = {};
// 旧写法
Object.preventExtensions(person); // 设置对象不可扩展
Reflect.preventExtensions(person);
person.a = 1;
console.log(person.a); // undefined
12.Reflect.isExtensible
表示当前对象是否可扩展,等价于Object.isExtensible
const person = {};
Reflect.preventExtensions(person);
Object.isExtensible(person) // 老写法 false
Reflect.isExtensible(person) // false
13.Reflect.ownKeys
用于返回对象的所有属性,包括Symbol属性
const person = {
age:18,
[Symbol('name')]:'song'
};
console.log(Object.getOwnPropertyNames(person)); // [ 'age' ] 老写法
console.log(Object.getOwnPropertySymbols(person)); // [ Symbol(name) ] 老写法
console.log(Reflect.ownKeys(person)); // [ 'age', Symbol(name) ]
到此我们把Reflect的所有用法已经讲解完毕,内容比较干!其实核心就是将原有的Object的部分方法放到了Reflect上,至于难度完全没看到!