ES6专栏 - Reflect
目录:
-
Reflect概述
-
Reflect上的静态方法
-
Reflect.get(target, prop, receiver)
-
Reflect.set(target, prop, value, receiver)
-
Reflect.has(target, prop)
-
Reflect.deleteProperty(target, prop)
-
Reflect.defineProperty(target, prop, desc)
-
Reflect.getPrototypeOf(target)
-
Reflect.setPrototypeOf(target, proto)
-
Reflect.getOwnPropertyDescriptor(target, prop)
-
Reflect.construct(target, args)
-
Reflect.apply(target, this, args)
-
Reflect.isExtensible(target)
-
Reflect.preventExtension(target)
-
Reflect.ownKeys(target)
-
Reflect概述
Object这哥们我们已经是很熟悉了吧, Object上面有着特别多的方法和属性, 如下图
但是我们仔细观察会发现, 这其中的属性特别的杂乱无章, 比如Object.freeze这种方法我们平时用的比较多的, 叫做外部方法, 但是Object.defineProperty大部分时间我们都用不到归类为语言内部方法, 甚至还有一些命令型操作, 比如delete xxx, prop in obj,这些方法性质不太一样却被放在一起, 让人摸不着头脑, 所以Es6推出了Reflect来单独管理这些内部方法
Reflect是Es6为了更好的让我们操作对象从而提供的新的api, Reflect的设计目的官方说到是如下几点
-
将Object对象上的一些明显属于语言内部的方法(比如Object.defineProperty)放进Reflect对象中, 现阶段为了兼容过去的代码, 过去所有的语言内部方法均在Object和Reflect上同时部署, 但在未来, 新的语言内部方法只会在Reflect上部署, 这样将有利于开发者更好的将两种类型的方法分开
-
修改某些Object方法的返回结果, 让其返回结果变得更加合理, 比如Object.defineProperty在无法定义属性的时候会报错, 但是Reflect.defineProperty则会返回一个false不报错
-
让命令式操作都变成函数行为, 比如之前我们判断一个对象是否包含某个属性是prop in obj, 而在Reflect中, 则用has方法进行代替, 函数式操作比命令式操作更加的语义化和标准化
-
让ES6中的Proxy可以和Reflect一一对应, 只要是Proxy上代理的方法都可以在Relect上找到对应的方法, 这就方便了Proxy更方便的调用Reflect方法来进行默认行为
const obj = { name: 'loki' } const handler = { get(target, prop) { return Reflect.get(target, prop); } }
总之, Reflect现在已经接管了Object上的所有语言内部方法, 而在未来新的内部方法也只会部署在Reflect上, 当下如果你还用不惯Reflect, 你可以继续使用Object, 但是未来你必须知道Reflect的重要性
Reflect上的静态方法
Reflect是一个对象, 他不能进行new操作, 所以Reflect只有静态方法, 没有实例方法, Reflect身上总共13中静态方法, 跟Proxy构造函数的实例方法一一对应, 我们来一一看看每个方法的具体作用
-
Reflect.get(target, prop, receiver)
Reflect.get方法主要是用于查找某个对象身上的某个属性, 如果未查找到则返回undefined
该方法接受三个参数
-
target: 被查找目标对象
-
prop: 被查找属性
-
recevier: 如果获取的prop属性被部署了getter读取函数, 该参数会作为读取函数执行时的this指向
小提示
读取函数getter: 给对象部署getter的方式有很多种, 比如Object.defineProperty, 亦或者proxy和直接在对象属性前部署get关键字
我们来看两个Reflect.get的实例
const obj = { name: 'loki' } const newObj = {}; // 使用Object.defineProperty给obj的name属性部署getter方法 Object.defineProperty(newObj, 'name', { get() { console.log(this); // this => {name: 'thor'} return this.name; } }) console.log(Reflect.get(obj, 'name', {name: 'thor'})); // 拿到的不是loki是thor
const obj = { name: 'loki', // 在属性前加上get关键字也是相当于给name部署了getter读取 get name() { console.log(this); // 下面的输出语句一执行这里就输出{name: 'thor'}; return this.name; } } console.log(Reflect.get(obj, 'name', {name: 'thor'})); // thor
-
-
Reflect.set(target, prop, value, receiver)
Reflect.set方法是给对象的赋值操作
该方法接收四个参数
-
target: 目标对象
-
prop: 要进行赋值的属性
-
value: 要赋予的新的value
-
receiver: 同get的receiver, 如果对象的该属性被部署了setter, 则该receiver为部署setter方法执行时的this指向
const obj = { name: 'loki', set name(newVal) { console.log(newVal); // 下面的Reflect.set一执行,这里输出andy console.log(this, this.name); // 这里输出{name: 'thor'}, thor return this.name = newVal; } } var secObj = { name: 'thor' } console.log(Reflect.set(obj, 'name', 'andy', secObj)); // true console.log(secObj); // {name: 'andy'}
如果不部署setter的话, 赋值挺简单的
const obj = { name: 'loki' } console.log(Reflect.set(obj, 'name', 'andy')); // true console.log(obj); // {name: 'andy'}
-
-
Reflect.has(target, prop)
Reflect.has方法主要是将Object对象中的命令
prop in target
变成了方法的模式, 更利于我们的学习和举一反三, 该方法的功能是判断一个对象中有没有某个属性Reflect.has接收两个参数
-
target: 目标被查找对象
-
prop: 需要查找的属性
const obj = { name: 'loki' } // 无Reflect之前判断obj中有无name属性是如下 console.log('name' in obj); // true // Reflect操作如下 cosnole.log(Reflect.has(obj, 'name')); //true
-
-
Reflect.deleteProperty(target, prop)
Reflect.deleteProperty主要用来将之前Object对象中的delete命令操作变成方法了, 用于删除对象上的某个属性, 删除恒功返回true
该方法接收两个参数
-
target: 被查找的目标对象
-
prop: 需要删除的属性
const obj = { name: 'loki', age: 18 } // 无Reflect之前的删除操作 console.log(delete obj.age); // true console.log(obj); // {name: 'loki'} // 使用Reflect的操作 console.log(Reflect.deleteProperty(obj, 'name')); // true console.log(obj); // {}
-
-
Reflect.construct(target, args);
Reflect.construct等同于之前我们没有Reflect对象时对一个构造函数的实例化, 也就是代替的new target(arg)操作, 该方法返回值为构造好的实例
该方法接收两个参数
-
target: 目标函数(必须是函数)
-
args: 调用构造函数时需要传入的参数, 单个参数可以直接写, 多个参数必须写成数组形式
function Person(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } // 不使用Reflect进行构造 const fstPerson = new Person('loki', 18, 'male'); // 使用Reflect const secPerson = Reflect.construct(Person, ['thor', 17, 'male']); console.log(fstPerson); // Person {name: "loki", age: 18, sex: "male"} console.log(secPerson); // Person {name: "thor", age: 17, sex: "male"}
-
-
Reflect.getPrototypeOf(target)
该方法用来对应之前Object.getPrototypeOf,用来获取某个对象的__proto__指向的原型
该方法接收一个参数
- target: 目标对象
Person.prorotype = { lastName: 'Curry' } function Person() {}; const person = new Person(); // 不用Reflect的写法 const fstProto = Object.getPrototypeOf(person); console.log(fstProto, fstProto === person.__proto__); // {constructor: ƒ Person(), __proto__: Object}, true console.log(fstProto === Person.prototype); // true // 用Reflect const secProto = Reflect.getPrototypeOf(person); console.log(secProto, secProto === person.__proto__); // {constructor: ƒ Person(), __proto__: Object}, true console.log(secProto === Person.prototype); // true console.log(secProto === fstProto); // true
小提示
这个Reflect.getPrototypeOf和Object.getPrototypeOf吧, 其实还有一点点小区别, Object.getPrototypeOf的参数如果不是一个对象的话会调用包装类进行包装成对象再做下一步计算, 而Reflect.getPrototypeOf会直接报错
-
Reflect.setPrototypeOf(target, proto)
这个方法用来对应的就是Object.setPrototyeOf, 用来给某一个对象设置新的__proto__指向
该方法接收两个参数
-
target: 目标对象
-
proto: 新的原型对象(由于__proto__就是一个属性, 所以你想写什么类型的值都可以, 只是一般用来设置新的原型对象)
Person.prototype = { lastName: 'Curry' } function Person() {} const fstPerson = new Person(); console.log(fstPerson.lastName); // Curry, 因为原型上有这个属性 // 我把它的原型指向更改了 Reflect.setPrototypeOf(fstPerson, {lastName: 'Kate'}); console.log(fstPerson.lastName); // Kate
同样如果第一个参数不是对象, Object.setPrototypeOf会进行包装类, 而Reflect.setPrototypeOf会报错
-
-
Reflect.apply(func, this, args)
这哥们对应的是Object.prototype.apply方法, 就是用来更改this指向的
该方法接收三个参数
-
func: 要更改this指向的函数
-
this: 新的this指向
-
args: 实参列表, 数组形式
const obj = { nameList: ['andy', 'lacus', 'amy'], printNames() { this.nameList.forEach(name => console.log(name)) } } console.log(obj.printNames()); // andy, lacus, amy // 不使用Reflect改变this指向 obj.printNames.apply({nameList: ['loki', 'thor']}); // loki, thor // 使用Reflect更改this指向 Reflect.apply(obj.printNames, {nameList: ['sam', 'aux']},[]); // sam, aux
小提示
在目前的版本中, Reflect.apply方法的第三个参数必须给值, 而且类型必须是数组, 如果你没有相应的参数传递, 那么你需要写个空数组, 未来可能会有变化, 毕竟这个设置不太合理
-
-
Reflect.defineProperty(target, prop, desc)
Reflect.defineProperty直接对标Object.defineProperty, 用来给某个对象的某个属性进行代理
该方法接收三个参数
-
target: 目标对象
-
prop: 目标属性
-
desc: 对该属性的配置
const obj = { name: 'loki', age: 18 } // 不使用Reflect let fstObj = {}; Object.defineProperty(fstObj, 'name', { get() { console.log('我是通过Object.defineProperty设置的代理'); return obj.name } }) // 使用Reflect let secObj = {}; Reflect.defineProperty(secObj, 'age', { get() { console.log('我是通过Reflect.defineProperty设置的代理'); return obj.age } }) console.log(fstObj.name); // 上方的输出语句依次输出 // 我是通过Object.defineProperty设置的代理 // loki console.log(secObj.age); // 上方的语句依次输出 // 我是通过Reflect.defineProperty设置的代理 // 18
-
-
Reflect.getOwnPropertyDescirptor(target, prop)
该方法对应的就是Object.getOwnPropertyDescirptor方法, 用来获取某个对象上某属性的描述对象
它接收两个参数
-
target: 目标对象
-
prop: 要查找的属性
const obj = { name: 'loki', age: 18 } Reflect.defineProperty(obj, 'name', { enumerable: false, configurable: false, writeable: false }) const obj = { name: 'loki', age: 18 } Reflect.defineProperty(obj, 'name', { enumerable: false, configurable: false, writable: false }) // 不使用Reflect console.log(Object.getOwnPropertyDescriptor(obj, 'name')); // {value: "loki", writable: false, enumerable: false, configurable: false} // 使用Reflect console.log(Reflect.getOwnPropertyDescriptor(obj, 'age')); // {value: 18, writable: true, enumerable: true, configurable: true}
-
-
Reflect.isExtensible(target)
该方法对标Object.isExtensible, 返回一个布尔值, 用来表示该方法是不是可扩展的, 所谓可扩展的就是可不可以在这个方法上增加新的属性
该方法接收一个参数
- target: 目标对象
const obj = { name: 'loki' } Object.freeze(obj); // 不用Reflect.isExtensible console.log(Object.isExtensible(obj)); // false // 使用Reflect console.log(Reflect.isExtensible(obj)); // false
小提示
如果一个对象是不可扩展的,仅代表我们不能给他增加新的属性, 而对原有的属性进行修改和删除是支持的, 而上方我用的是Object.freeze, 该方法一旦执行就是对象不可增删改了, 所以单纯让对象变得不可扩展应该使用Reflect.preventExtensions
-
Reflect.preventExtensions(target);
该方法对应Object.preventExtensions, 用来使一个对象变得不可扩展的
它接收一个参数
- target: 目标对象
const obj = { name: 'loki' } // 不用Reflect console.log(Object.preventExtensions(obj)); // {name: 'loki'} // 使用Reflect console.log(Reflect.preventExtensions(obj)); // true
小提示
Object.preventExtensions会返回参数对象本身, 而Reflect.preventExtensions会返回true
-
Reflect.ownKeys(target);
Reflect.ownKeys基本等于Object.getOwnPropertyNames, Object.getOwnPropertySymbols之和,用于返回该对象所有的key名(包括Symbol)
该方法接收一个参数
- target: 目标对象
const obj = { [Symbol.for('name')]: 'loki', age: 18, sex: 'male' } // 不使用Reflect console.log(Object.getOwnPropertyNames(obj)); // ["age", "sex"] console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(name)] // 使用Reflect console.log(Reflect.ownKeys(obj)); // ["age", "sex", Symbol(name)]
小提示
Object上有三种遍历key值的方法, 分别是Object.keys, Object.getOwnPropertyNames和Object.getOwnPropertySymbols, 前两者的区别主要是Object.keys只能返回enumerable为true的值
const obj = { [Symbol.for('name')]: 'loki', age: 18, sex: 'male' } Reflect.defineProperty(obj, 'sex', { enumerable: false }) console.log(Object.keys(obj)); // ['age'] console.log(Object.getOwnPropertyNames(obj)); // ['age', 'sex'] console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(name)]