ES中,新增了Proxy和Reflect两个内置对象,并且经常结合起来使用,我个人觉得先理解了Reflect对象,Proxy就更好理解了。
一、Reflect对象
Reflect 是ES6提供的一个内置对象,内部包括一系列方法,用来操作对象
。
这些方法并没有什么新的功能,都是实现对象的属性的获取、设置、删除、遍历等等,这么做的目的使对象的操作更加的严谨、便捷、统一。
Reflect让对对象的操作都变为函数式编程,将一些 Object 对象上的方法转移到 Reflect 上,使得操作对象更加统一和易于理解。通过这种方式,实现了对 Object 上方法的封装和统一.
如:
配置对象一个不可配置的属性,Object.defineProperty()方法会报错,Reflect.defineProperty()方法会返回false
将命令式写法 PropertyKey in target ,写成Reflect.has(target, PropertyKey) ;
delete target[PropertyKey],写成Reflect.deleteProperty(target, PropertyKey)
Reflect的方法
Reflect是一个对象,并不是一个构造函数,没有实例方法。
这些方法的作用包括:
1. 读取属性值
Reflect.get(target,key,receiver),相当于 target[key]
参数:目标对象,属性名和Proxy实例本身
let p={
firstName:'Lily',
// age属性部署了getter读取函数
get age(){
//this返回的是Reflect.get的receiver参数对象
return this.age
}
};
console.log(p.firstName); //旧写法
console.log(Reflect.get(p,'firstName')); //新写法
console.log(Reflect.get(p,'age',{age:18})); //新写法
2. 设置对象的属性值
Reflect.set(target,PropertyKey,value) ,相当于 target[key]= value
let p={age:18};
console.log(p.age=20); //旧写法,返回20
Reflect.set(p,'age',20); //新写法,返回true
console.log(p);
3. 检测对象是否包含某个属性
Reflect.has(target, PropertyKey) ,相当于PropertyKey in target
let p={age:18};
console.log('age' in p); //旧写法,true
console.log(Reflect.has(p,'age')); //新写法,true
4. 删除对象的属性
Reflect.deleteProperty(target, PropertyKey) ,相当于delete target[PropertyKey]
let p={firstName:'Lily',age:18};
delete p.firstName; // 旧写法
Reflect.deleteProperty(p,'age'); // 新写法
5. 不使用new,来调用构造函数的方法
Reflect.construct(target,argumentslist),相当于new target(…argumentslist)
其中target是构造函数,argumentslist是参数构成的数组
function Person(name,age) {
this.name = name;
this.age=age;
}
let p1=new Person('Lily',18); // 旧写法
let p2=Reflect.construct(Person,['Lilyan',20]); // 新写法
6. 读取对象的原型属性
Reflect.getPrototypeOf(target),相当于Object.getPrototypeOf(target)
function Chinese(name){
this.name=name;
}
Chinese.prototype = {
nationality:'中国'
};
let p=new Chinese('张三');
console.log(Object.getPrototypeOf(p)); //旧方法 {nationality: "中国"}
console.log(Reflect.getPrototypeOf(p)); //新方法 {nationality: "中国"}
7. 设置对象的原型属性
Reflect.setPrototypeOf(target, proto),相当于Object.setPrototypeOf(target, proto)
function Chinese(name){this.name=name};
let p1=new Chinese('张三');
Object.setPrototypeOf(p1, {nationality:'中国'}); //旧方法
Reflect.setPrototypeOf(p1,{nationality:'Chinese'}); //新方法
8. 通过指定的参数列表发起对目标函数的调用
Reflect.apply(target, thisArgument, argumentsList) 相当于Function.prototype.apply()
target是目标函数;
thisArgument是target函数调用时绑定的this对象;
argumentsList是target函数调用时传入的实参列表,该参数应该是一个类数组的对象。
返回值是调用完带着指定参数和 this 值的给定的函数后返回的结果。
//借用Math对象的max方法,获得数组中的最大值
console.log(Math.max.apply(undefined,[1,2,3])); //旧方法
console.log(Reflect.apply(Math.max, undefined, [1,2,3])); //新方法
9. 添加/修改对象属性的描述
Reflect.defineProperty(target, propertyKey, attributes) 相当于 Object.definePropertytarget,PropertyKey,attributes)
attributes是要定义或修改的属性的描述。
let p={age:18};
Object.defineProperty(p,'age',{enumerable:false}); //旧方法
Reflect.defineProperty(p,'age',{writable:false}); //新方法
10. 返回指定属性的属性描述符
Reflect.getOwnPropertyDescriptor(target, propertyKey) 相当于Object.getOwnPropertyDescriptor(target, propertyKey)
let p={age:18};
console.log(Object.getOwnPropertyDescriptor(p, 'age')); //旧方法
console.log(Reflect.getOwnPropertyDescriptor(p, 'age'));//新方法
11. 返回一个对象包括Symbol 属性在内的全部的自有属性的键名组成的数组
Reflect.ownKeys(target) 相当于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
Object.getOwnPropertyNames(Math).concat(Object.getOwnPropertySymbols(Math)); //旧写法
Reflect.ownKeys(Math); //新写法
12. 阻止新属性添加到对象(使对象不能扩展)
Reflect.preventExtensions(target),相当于Object.preventExtensions(target)
let p = {
firstName:'Lily'
};
Object.preventExtensions(p); //旧写法
Reflect.preventExtensions(p); //新写法
p.age=18; //静默失败
console.log(p);
13. 判断一个对象是否可扩展
Reflect.isExtensible(target) 相当于与 Object.isExtensible(target)
let p = {
firstName:'Lily'
};
Reflect.preventExtensions(p);
console.log(Object.isExtensible(p));//旧写法
console.log(Reflect.isExtensible(p))//新写法
二、Proxy
Proxy的意思是代理,ES6中提供了Proxy构造函数,Proxy的作用可以理解成在目标对象之前架设一层“拦截”,外界对该对象的操作,都必须先通过这层拦截,这种方式可以拦截外界对对象的操作进行过滤和改写,通过 Proxy是实现数据劫持的方法之一。
拦截的目的是处理,通常使用Reflect来处理,也就是说Proxy负责拦截,Reflect负责处理,并不是必须使用Reflect返回数据,而是推荐这样做。
这里说的外界对该对象的操作,指的就是和Reflect的方法对应的13种操作。
Proxy的语法
let proxy = new Proxy(target, handler);
外界每次通过Proxy对象访操作arget对象时,就会经过 handler对象,所以在通过重写handler对象中的方法来做一些拦截的操作。
- new Proxy() 表示生成一个Proxy实例
- target表示所要拦截的目标对象
- handler也是一个对象,用来定制拦截行为。
Proxy的作用,我们举个例子,西夏国的银川公主要选驸马,却不亲自出面,任命了婢女小蕾代理她,驸马候选人想获得公主的信息,设置自己为公主的husband,都要通过代理小蕾。
function Princess(name,nationality,age) {
this.name=name;
this.nationality=nationality;
this.age=age;
}
Princess.prototype.identity='princess';
let yinChuan=new Princess("银川","西夏",16);
let handler = {};
let xiaoLei = new Proxy(yinChuan,handler);
console.log(xiaoLei.age); //16
上面的代码中handler是空的,没有定义拦截行为,对代理对象的操作等同于直接操作原对象target。
下面开始定义拦截行为handler
/*1 get 用于拦截某个属性的读取操作
参数:目标对象,属性名和Proxy实例本身
返回:属性值*/
get(target,PropertyKey,receiver){
console.log(`拦截了${PropertyKey}的读取,将返回:${Reflect.get(target, PropertyKey,receiver)}`);
return Reflect.get(target, PropertyKey,receiver);
},
/*2 set 用来拦截某个属性的赋值操作
参数:目标对象,属性名,属性值和Proxy实例本身,其中最后一个参数可选
返回值:布尔值*/
set(target,PropertyKey,value,receiver){
console.log(`拦截了属性${PropertyKey}的赋值,将返回:${Reflect.set(target,PropertyKey,value)}`);
return Reflect.set(target,PropertyKey,value);
},
//3 has 拦截检测对象是否包含某个属性 PropertyKey in proxy
has(target,PropertyKey){
console.log(`拦截了检测对象中是否包含属性${PropertyKey}的操作,将返回${Reflect.has(target,PropertyKey)}`);
return Reflect.has(target,PropertyKey);
},
//4 deleteProperty 拦截删除属性的操作
deleteProperty(target,PropertyKey){
console.log(`拦截了删除属性${PropertyKey},将返回${Reflect.deleteProperty(target,PropertyKey)}`);
return Reflect.deleteProperty(target,PropertyKey);
},
//6 拦截读取对象的原型属性的操作
getPrototypeOf(target){
console.log(`拦截了设置对象的原型属性的操作,将返回`,Reflect.setPrototypeOf(target,proto));
return Reflect.getPrototypeOf(target);
},
//7 拦截设置对象的原型属性的操作
setPrototypeOf(target,proto){
console.log(`拦截了设置对象的原型属性的操作`);
return Reflect.setPrototypeOf(target,proto);
},
//9 拦截定义对象属性的描述的操作
defineProperty(target, PropertyKey, attributes){
console.log(`拦截了定义对象属性${PropertyKey}的描述的操作,将返回${Reflect.defineProperty(target,PropertyKey,attributes)}`);
return Reflect.defineProperty(target,PropertyKey,attributes);
},
//10 拦截获取指定属性的属性描述符的操作
getOwnPropertyDescriptor(target,propertyKey){
console.log(`拦截了获取${propertyKey}的属性描述符的操作,将返回`,Reflect.getOwnPropertyDescriptor(target,propertyKey));
return Reflect.getOwnPropertyDescriptor(target,propertyKey);
},
//11 拦截获取对象包括Symbol属性在内的全部的自有属性的键名组成的数组的操作
ownKeys(target){
console.log(`拦截了获取对象自有属性操作,将返回`,Reflect.ownKeys(target));
return Reflect.ownKeys(target);
},
//12 拦截使对象不能拓展的操作
preventExtensions(target){
console.log(`拦截了使对象不能拓展的操作,将返回${Reflect.preventExtensions(target)}`);
return Reflect.preventExtensions(target);
},
//13 拦截判断一个对象是否可扩展的操作
isExtensible(target){
console.log(`拦截判断对象是否可扩展的操作,将返回${Reflect.isExtensible(target)}`);
return Reflect.isExtensible(target);
}
}
打印对象xiaoLei可以看到:
这些配置有没有作用,我们通过下面的操作来验证一下:
xiaoLei.name; //拦截了name的读取,将返回:银川公主
Reflect.get(xiaoLei,'age'); //拦截了age的读取,将返回:16
'name' in xiaoLei; //拦截了检测对象中是否包含属性name的操作,将返回true
Reflect.has(xiaoLei,'name'); //拦截了检测对象中是否包含属性name的操作,将返回true
delete xiaoLei.husband; //拦截了删除属性husband,将返回true
Reflect.deleteProperty(xiaoLei,'nationality'); //拦截了删除属性nationality,将返回true
Object.getPrototypeOf(xiaoLei); //拦截了读取对象的原型属性的操作,将返回 {identity: "princess", constructor: ƒ}
Reflect.setPrototypeOf(xiaoLei,{identity:'公主'}); //拦截了设置对象的原型属性的操作,将返回 true
Reflect.getPrototypeOf(xiaoLei); //拦截了读取对象的原型属性的操作,将返回 {identity: "公主"}
Object.defineProperty(xiaoLei,'age',{writable:false}); //拦截了定义对象属性age的描述的操作,将返回true
Reflect.defineProperty(xiaoLei,'age',{writable:false}); //拦截了定义对象属性age的描述的操作,将返回 true
Object.getOwnPropertyDescriptor(xiaoLei,'age'); //拦截了获取age的属性描述符的操作,将返回 {value: 16, writable: false, enumerable: true, configurable: true}
Reflect.getOwnPropertyDescriptor(xiaoLei,'age'); //拦截了获取age的属性描述符的操作,将返回 {value: 16, writable: false, enumerable: true, configurable: true}
Reflect.ownKeys(xiaoLei); //拦截了获取对象自有属性操作,将返回 (2) ["name", "age"]
Object.getOwnPropertyNames(xiaoLei); //拦截了获取对象自有属性操作,将返回 (2) ["name", "age"]
Object.getOwnPropertySymbols(xiaoLei); //拦截了获取对象自有属性操作,将返回 (2) ["name", "age"]
Object.preventExtensions(xiaoLei); //拦截了使对象不能拓展的操作,将返回true
Reflect.preventExtensions(xiaoLei); //拦截了使对象不能拓展的操作,将返回true
Object.isExtensible(xiaoLei); //拦截判断对象是否可扩展的操作,将返回false
Reflect.isExtensible(xiaoLei); //拦截判断对象是否可扩展的操作,将返回false
与Reflect的静态方法对应的,Proxy支持拦截的操作,一共有13种:
序号 | Reflect方法 | Proxy方法 | Proxy方法描述 |
---|---|---|---|
1 | Reflect.get(target,PropertyKey,receiver) | get(target, PropertyKey, receiver) | 拦截某个属性的读取操作 |
2 | Reflect.set(target,PropertyKey,value) | set(target, PropertyKey, value, receiver) | 拦截某个属性的赋值操作 |
3 | Reflect.has(target, PropertyKey) | has(target, PropertyKey) | 拦截检测对象是否包含某个属性 |
4 | Reflect.deleteProperty(target, PropertyKey) | deleteProperty(target, PropertyKey) | 拦截删除属性的操作 |
5 | Reflect.construct(target,argumentslist) | construct(target, args) | 拦截实例化调用构造函数的操作,代理的对象为function时有效 |
6 | Reflect.getPrototypeOf(target) | getPrototypeOf(target) | 拦截读取对象的原型属性的操作,返回原型对象 |
7 | Reflect.setPrototypeOf(target, prototype) | setPrototypeOf(target, prototype) | 拦截设置对象的原型属性的操作 |
8 | Reflect.apply(target, thisArgument, argumentsList) | apply(target, thisArgument, argumentsList) | 拦截函数的调用call()、apply(),代理的对象为function时有效 |
9 | Reflect.defineProperty(target, propertyKey, attributes) | defineProperty(target, PropertyKey, attributes) | 拦截定义对象属性的描述的操作10 |
11 | Reflect.ownKeys(target) | ownKeys(target) | 拦截读取对象自身的属性的操作 |
12 | Reflect.preventExtensions(target) | preventExtensions(target) | 拦截使对象不能拓展的操作 |
13 | Reflect.isExtensible(target) | isExtensible(target) | 拦截判断一个对象是否可扩展的操作 |
其中5和8在代理的对象是为function时有效,我们来试一下
function Princess(name) {
this.name=name;
return `我是${this.name},年龄${this.age}`;
}
let handler={
//5 construct 拦截实例化调用的操作
construct(target,thisArg,proxy){
console.log(`拦截了实例化${target.name}的操作,将返回`,Reflect.construct(target,thisArg,proxy));
return Reflect.construct(target,thisArg,proxy);
},
// 8 通过指定的参数列表发起对目标函数的调用,返回值是调用完带着指定参数和 this 值的给定的函数后返回的结果。
apply(target,thisArgument,argumentsList){
console.log(`拦截了通过指定的参数列表发起对函数${target.name}的调用,将返回`,Reflect.apply(target,thisArgument,argumentsList));
return Reflect.apply(target,thisArgument,argumentsList);
}
};
let pro=new Proxy(Princess,handler);
new pro("银川"); //拦截了实例化Princess的操作,将返回 Princess {name: "银川"}
Reflect.construct(pro,["银川"]); //拦截了实例化Princess的操作,将返回 Princess {name: "银川"}
pro.call({age:16},"银川"); //拦截了通过指定的参数列表发起对函数Princess的调用,将返回 我是银川,年龄16
pro.apply({age:16},["银川"]); //拦截了通过指定的参数列表发起对函数Princess的调用,将返回 我是银川,年龄16
Reflect.apply(pro,{age:16},["银川"]); //拦截了通过指定的参数列表发起对函数Princess的调用,将返回 我是银川,年龄16