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)]