ES6-用Proxy和Reflect操作对象

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方法描述
1Reflect.get(target,PropertyKey,receiver)get(target, PropertyKey, receiver)拦截某个属性的读取操作
2Reflect.set(target,PropertyKey,value)set(target, PropertyKey, value, receiver)拦截某个属性的赋值操作
3Reflect.has(target, PropertyKey)has(target, PropertyKey)拦截检测对象是否包含某个属性
4Reflect.deleteProperty(target, PropertyKey)deleteProperty(target, PropertyKey)拦截删除属性的操作
5Reflect.construct(target,argumentslist)construct(target, args)拦截实例化调用构造函数的操作,代理的对象为function时有效
6Reflect.getPrototypeOf(target)getPrototypeOf(target)拦截读取对象的原型属性的操作,返回原型对象
7Reflect.setPrototypeOf(target, prototype)setPrototypeOf(target, prototype)拦截设置对象的原型属性的操作
8Reflect.apply(target, thisArgument, argumentsList)apply(target, thisArgument, argumentsList)拦截函数的调用call()、apply(),代理的对象为function时有效
9Reflect.defineProperty(target, propertyKey, attributes)defineProperty(target, PropertyKey, attributes)拦截定义对象属性的描述的操作10
11Reflect.ownKeys(target)ownKeys(target)拦截读取对象自身的属性的操作
12Reflect.preventExtensions(target)preventExtensions(target)拦截使对象不能拓展的操作
13Reflect.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

本节内容请参考:
Javascript面向对象-对象属性详解
JavaScript原生对象-Object对象详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值