ES6+ Reflect(一)
1. 前言
任何一門語言在走向成熟的過程都是趨向精細化和規范化,在 API 設計之初滿足當時的需求場景。隨著前端的飛速發展,軟件復雜度的提升,很多 API 在使用過程中存在很多使用別扭的情況,不符合軟件開發的規范。上一節我們學習了 Proxy,Proxy 的設計目的是為了取代 Object.defineProperty,優化性能,使得數據劫持的過程更加規范。
本節我們將學習 ES6 新的全局對象 —— Reflect(反射),首先我們要了解一下,為什么會新添加這么一個全局對象?Reflect 上的一些函數基本上都可以在 Object 上找到,找不到的,也是可以通過對對象命令式的操作去實現的;那么為什么還要新添加一個呢?本節我們將學習 Reflect 的相關知識。
2. 基礎知識
Reflect 是一個內置的對象,它提供了攔截 JavaScript 操作的方法。這些方法與 Proxy 中的 handlers 方法相同。與大多數全局對象不同 Reflect
并非一個構造函數,所以不能通過 new 運算符對其進行調用,或者將 Reflect
對象作為一個函數來調用。Reflect
的所有屬性和方法都是靜態的(類似 JSON
或者 Math
等對象)。
2.1 基本用法
Reflect 可以檢查對象上是否存在特定屬性,可以使用 Reflect.has()
方法檢測。
let key = Symbol.for('a');
const obj = {
name: 'imooc',
lession: 'ES6 Wiki',
[key]: 100
}
console.log(Reflect.has(obj, 'name')); // true
console.log(Reflect.has(obj, 'age')); // false
可以使用 Reflect.get()
方法獲取對象上的屬性值。
console.log(Reflect.get(obj, 'name')); // imooc
可以使用 Reflect.set()
方法為對象添加一個新的屬性。
const res = Reflect.set(obj, 'age', 7);
console.log(res); // true
console.log(obj); // {name: "imooc", lession: "ES6 Wiki", age: 7}
使用 Reflect.ownKeys()
方法獲取對象上的自有屬性。
console.log(Object.keys(obj)); // ["name", "lession"]
console.log(Reflect.ownKeys(obj)); // ["name", "lession", Symbol(a)]
上面的代碼可以看出,使用 Object.keys()
獲取不到屬性是 Symbol 的值。
2.2 返回值
Reflect 對象上的方法并不是專門為對象設計的,而是在語言層面的,它可以拿到語言內部的方法,和 Proxy 的結合可以實現元編程。并且每個操作都是有返回值的,上節我們使用 Proxy 簡單地實現了 Vue3 的響應式。但是在 Vue3 源碼中獲取和設置對象上的屬性使用的是 Reflect,Reflect 會返回一個狀態表示獲取和設置的成功與否。
// const res = target[key]; // 上節代碼
const res = Reflect.get(target, key); // 獲取target上屬性key的值
// target[key] = value; // 上節代碼
const result = Reflect.set(target, key, value); // 設置目標對象key屬性的值
上面的兩段代碼是 Vue3 中的源碼,因為在源碼中需要知道獲取或賦值的結果,因為可能獲取失敗。在 ES5 中如果想要監聽劫持屬性操作的結果需要使用 try...catch
的方式。
try {
Object.defineProperty(obj, prop, descriptor);
// success
} catch (e) {
// failure
}
Reflect 在操作對象時是有返回結果的,而 Object.defineProperty 是沒有返回結果的,如果失敗則會拋出異常,所以需要使用 try...catch
來捕獲異常。
2.3 使用函數代替命令式
Object 中操作數據時,有一些是命令式的操作,如:delete obj.a
、name in obj
,Reflect 則將一些命令式的操作如 delete
,in
等使用函數來替代,這樣做的目的是為了讓代碼更加好維護,更容易向下兼容;也避免出現更多的保留字。
// ES5
'assign' in Object // true
// ES6
Reflect.has(Object, 'assign') // true
delete obj.name; // ES5
Reflect.deleteProperty(obj, 'name'); // ES6
3. 靜態方法
Reflect 的出現是為了取代 Object 中一些屬于語言層面的 API,這些 API 在 Object 上也是可以找到的,并且它們的功能基本是相同的。上面我們也提到了 Reflect 和 Proxy 中 handlers 的方法是一一對應的,在很多場景中它門都是配套使用的。這里我們就來學習一下 Reflect 提供的靜態方法:
3.1 Reflect.get()
Reflect.get()
方法是從對象中讀取屬性的值,類似 ES5 中屬性訪問器語法: obj[key]
,但是它是通過調用函數來獲得返回結果的。
語法:
Reflect.get(target, propertyKey[, receiver])
- target:需要取值的目標對象;
- propertyKey:需要獲取的值的鍵值;
- receiver:如果 target 對象中指定了 getter,receiver 則為 getter 調用時的 this 值。
如果目標值 target 類型不是 Object
,則拋出一個 TypeError
。
// Object
var obj = { a: 1, b: 2 };
Reflect.get(obj, "a"); // 1
// Array
Reflect.get(["a", "b", "c"], 1); // "one"
第三個參數 receiver 是 this 所在的上下文,不傳時指的是當前對象,如果傳如一個人對象則 this 指向該對象。下面我們來看個實例:
let obj = {
name: 'imooc',
lesson: 'ES5 Wiki',
get info() {
console.log(`這是慕課 ${this.lesson}`);
return 0
}
};
Reflect.get(obj, 'info'); // 這是慕課 ES5 Wiki
Reflect.get(obj, 'info', {lesson: 'ES6 Wiki'}); // 這是慕課 ES5 Wiki
3.2 Reflect.set()
Reflect.set()
是在一個對象上設置一個屬性,類似 ES5 中屬性設置語法:obj[key] = value
,它也是通過調用函數的方式來對對象設置屬性的。
語法:
Reflect.set(target, propertyKey, value[, receiver])
- target:表示要操作的目標對象;
- propertyKey:表示要設置的屬性名;
- value:表示設置的屬性值;
- receiver:表示的是一個 this 值,如果我們在設置值的時候遇到 setter 函數,那么這個 receiver 值表示的就是 setter 函數中的 this 值。
這個函數會返回一個 Boolean 值,表示在目標對象上設置屬性是否成功。
// Object
var obj = {};
Reflect.set(obj, "name", "imooc"); // true
console.log(obj.name); // "imooc"
// Array
var arr = ["a", "b", "c"];
Reflect.set(arr, 2, "C"); // true
console.log(arr); // ["a", "b", "C"]
使用可以截斷數組:
var arr = ["a", "b", "c"];
Reflect.set(arr, "length", 2); // true
console.log(arr); // ["a", "b"]
當有 receiver 參數時,如果 receiver 對象中有 propertyKey 屬性,則會使用 receiver 對象中的值。
Reflect.set(obj, 'lession', 'ES5 Wiki', {lession: 'ES6 Wiki', age: 18});
console.log(obj); // {name: "imooc", lesson: "ES5 Wiki"}
3.3 Reflect.deleteProperty()
Reflect.deleteProperty()
方法允許刪除對象的屬性。它類似 ES5 中的 delete
操作符,但它也是一個函數,通過調用函數來實現。
語法:
Reflect.deleteProperty(target, propertyKey)
- target:表示要操作的目標對象;
- propertyKey:表示要刪除的屬性。
這個函數的返回值是一個 Boolean 值,如果成功的話,返回 true;失敗的話返回 false。我們來看下面的實例:
var obj = {
name: 'imooc',
lession: 'ES6 Wiki'
};
var r1 = Reflect.deleteProperty(obj, 'name');
console.log(r1); // true
console.log(obj); // {lession: "ES6 Wiki"}
var r2 = Reflect.deleteProperty(Object.freeze(obj), 'lession');
console.log(r2); // false
上面的例子中使用 Object.freeze()
方法來凍結 obj 對象使之不能被修改。
3.4 Reflect.has()
Reflect.has()
方法可以檢查一個對象上是否含有特定的屬性,這個方法相當于 ES5 的 in
操作符。
語法:
Reflect.has(target, propertyKey)
- target:表示要操作的目標對象;
- propertyKey: 屬性名,表示需要檢查目標對象是否存在此屬性。
這個函數的返回結果是一個 Boolean 值,如果存在就返回 true,不存在就返回 false。當然如果目標對象 (target) 不是一個對象,那么就會拋出一個異常。
Reflect.has({x: 0}, "x"); // true
Reflect.has({x: 0}, "y"); // false
// 如果該屬性存在于原型鏈中,也返回true
Reflect.has({x: 0}, "toString"); // true
這方法也可檢查構造函數的屬性。
function A(name) {
this.name = name || 'imooc';
}
// 在原型上添加方法
A.prototype.getName = function() {
return this.name;
};
var a = new A();
console.log('name' in a); // true
console.log('getName' in a); // true
let r1 = Reflect.has(a, 'name');
let r2 = Reflect.has(a, 'getName');
console.log(r1, r2); // true true
3.5 Reflect.ownKeys()
Reflect.ownKeys()
返回一個由目標對象自身的屬性鍵組成的數組。
語法:
Reflect.ownKeys(target)
- target:表示目標對象
如果這個目標對象不是一個對象那么這個函數就會拋出一個異常。這個數組的值等于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
我們來看下面的實例:
let a = Symbol.for('a');
let b = Symbol.for('b');
let obj = {
[a]: 10,
[b]: 20,
key1: 30,
key2: 40
};
let arr1 = Object.getOwnPropertyNames(obj);
console.log(arr1); // [ 'key1', 'key2' ]
let arr2 = Object.getOwnPropertySymbols(obj);
console.log(arr2); // [ Symbol(a), Symbol(b) ]
let arr3 = Reflect.ownKeys(obj);
console.log(arr3); // [ 'key1', 'key2', Symbol(a), Symbol(b) ]
4. 小結
本節主要學習了 ES6 新增的全局對象 Reflect
,它的目的是為了分離 Object 中屬于語言部分的內容,每個使用 Reflect
下的方法操作的對象都要返回值。 Reflect
對象和 Proxy
下的方法是一一對應的,二者配合可以實現很多功能。Vue3 中的數據響應就是使用的它們。