JS 反射機制及 Reflect 詳解

Leophen發表於2021-06-02

一、什麼是反射機制

反射機制是在編譯階段不知道是哪個類被載入,而是在執行的時候才載入、執行。
也就是說,反射機制指的是程式在執行時能夠獲取自身的資訊。
js 中的 apply 就是反射機制。

二、Reflect

1、Reflect 定義

Reflect 是一個內建的物件,用來提供方法去攔截 JavaScript 的操作。
Reflect 不是一個函式物件,所以它是不可構造的,也就是說它不是一個構造器,不能通過 new 操作符去新建或者將其作為一個函式去呼叫 Reflect 物件。
Reflect 的所有屬性和方法都是靜態的。

Reflect 內部封裝了一系列對物件的底層操作
Reflect 成員方法就是 Proxy 處理物件的預設實現

const proxy = new Proxy(obj, {
  get(target, property) {
    // 如果沒有定義 get 方法,那麼預設返回的就是 Reflect 的 get 方法
    return Reflect.get(target, property)
  }
})

2、Reflect API 彙總

Reflect 提供了一套用於操作物件的 API,我們之前操作物件可以用 Object 上面的一些方法,也可以用 in、delete 這種操作符,使用 Reflect 就統一了操作方式

handler ⽅法 預設調⽤ 功能
get Reflect.get() 獲取物件身上某個屬性的值
set Reflect.set() 在物件上設定屬性
has Reflect.has() 判斷一個物件是否存在某個屬性
deleteProperty Reflect.deleteProperty() 刪除物件上的屬性
getProperty Reflect.getPrototypeOf() 獲取指定物件原型的函式
setProperty Reflect.setPrototypeOf() 設定或改變物件原型的函式
isExtensible Reflect.isExtensible() 判斷一個物件是否可擴充套件 (即是否能夠新增新的屬性)
preventExtensions Reflect.preventExtensions() 阻止新屬性新增到物件
getOwnPropertyDescriptor Reflect.getOwnPropertyDescriptor() 獲取給定屬性的屬性描述符
defineProperty Reflect.defineProperty() 定義或修改一個物件的屬性
ownKeys Reflect.ownKeys() 返回由目標物件自身的屬性鍵組成的陣列
apply Reflect.apply() 對一個函式進行呼叫操作,同時可以傳入一個陣列作為呼叫引數
construct Reflect.construct() 對建構函式進行 new 操作,實現建立類的例項
.preventExtensions Reflect.preventExtensions() 阻止新屬性新增到物件

3、.apply()

Reflect.apply(target, thisArgument, argumentsList)

  • target:目標函式(必選)
  • thisArgument:target 函式呼叫時繫結的 this 物件(可選)
  • argumentsList:target 函式呼叫時傳入的實參列表,該引數應該是一個類陣列的物件(可選)

① ES5 用法

先指定方法,再去呼叫 apply

Math.floor.apply(null, [1.72])  // 1

② ES6 用法

先傳遞 apply,再指定是哪個方法

Reflect.apply(Math.floor, null, [1.72])  // 1

靜態掃描時 Math.floor 是沒有被執行,等到執行時再動態的將 Math.floor 作為引數傳進來的

③ 實際應用

// ES5 用法
let price = 101.5
if (price > 100) {
  price = Math.floor.apply(null, [price])
} else {
  price = Math.ceil.apply(null, [price])
}

price  // 101
// ES6 用法
let price = 101.5

Reflect.apply(price > 100 ? Math.floor : Math.ceil, null, [price])  // 101

4、.construct()

使用反射的方式去實現建立類的例項,類似於 new target(…args)
Reflect.construct(target, argumentsList[, newTarget])

  • target:被執行的目標函式(必選)
  • argumentsList:呼叫建構函式的陣列或者偽陣列(可選)
  • newTarget:該引數為建構函式, 參考 new.target 操作符,如果沒有 newTarget 引數, 預設和 target 一樣(可選)

① ES5 用法

let a = new Date()

a.getTime()  // 1632632744483

② ES6 用法

let b = Reflect.construct(Date, [])

b.getTime()  // 1632632744484

5、.defineProperty()

靜態方法 Reflect.defineProperty() 基本等同於 Object.defineProperty() 方法
Reflect.defineProperty(target, propertyKey, attributes)

  • target:目標物件(必選)
  • propertyKey:要定義或修改的屬性的名稱(可選)
  • attributes:要定義或修改的屬性的描述(可選)

① ES5 用法

const student = {}
const r = Object.defineProperty(student, 'name', { value: 'Mike' })

student  // {name: "Mike"}
r  // {name: "Mike"}

② ES6 用法

const student = {}
const r = Reflect.defineProperty(student, 'name', { value: 'Mike' })

student  // {name: "Mike"}
r  // true

這兩個方法效果上來看是一摸一樣的,都可以改變一個物件的值
區別在於返回值不同:Object是返回這個值,Reflect是返回true

PS: 在 W3C 中,以後所有的 Object 上面的方法,都會慢慢遷移到 Reflect 物件,可能以後會在 Object 上面移除這些方法

6、.deleteProperty()

Reflect.deleteProperty 允許你刪除一個物件上的屬性,返回一個 Boolean 值表示該屬性是否被成功刪除,它幾乎與非嚴格的 delete operator 相同
Reflect.deleteProperty(target, propertyKey)

  • target:刪除屬性的目標物件
  • propertyKey:將被刪除的屬性的名稱

① ES5 用法

const obj = { x: 1, y: 2 }
const a = delete obj.x

obj  // {y: 2}
a  // true

② ES6 用法

const obj = { x: 1, y: 2 }
const a = Reflect.deleteProperty(obj, 'x')

obj  // {y: 2}
a  // true

7、.get()

Reflect.get() 方法的工作方式,就像從 object (target[propertyKey]) 中獲取屬性,但它是作為一個函式執行的
Reflect.get(target, propertyKey[, receiver])

① ES5 用法

const obj = { x: 1, y: 2 }

obj.x  // 1
obj['x']  // 1

② ES6 用法

const obj = { x: 1, y: 2 }

Reflect.get(obj, 'x')  // 1

Reflect.get(['a', 'b', 'c'], 1)  // b

8、.getOwnPropertyDescriptor()

靜態方法 Reflect.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor() 方法相似
如果在物件中存在,則返回給定的屬性的屬性描述符,否則返回 undefined
Reflect.getOwnPropertyDescriptor(target, propertyKey)

① ES5 用法

const obj = { x: 1, y: 2 }

Object.getOwnPropertyDescriptor(obj, 'x')
// {value: 1, writable: true, enumerable: true, configurable: true}

② ES6 用法

const obj = { x: 1, y: 2 }

Reflect.getOwnPropertyDescriptor(obj, 'x')
// {value: 1, writable: true, enumerable: true, configurable: true}

Reflect.getOwnPropertyDescriptor({ x: 'hello' }, 'y')
// undefined

Reflect.getOwnPropertyDescriptor([], 'length')
// {value: 0, writable: true, enumerable: false, configurable: false}

③ 對比

如果 Reflect.getOwnPropertyDescriptor 的第一個引數不是一個物件(一個原始值),那麼將造成 TypeError 錯誤
而對於 Object.getOwnPropertyDescriptor,非物件的第一個引數將被強制轉換為一個物件處理

Reflect.getOwnPropertyDescriptor("foo", 0);
// TypeError: "foo" is not non-null object

Object.getOwnPropertyDescriptor("foo", 0);
// { value: "f", writable: false, enumerable: true, configurable: false }

9、.getPrototypeOf()

靜態方法 Reflect.getPrototypeOf()Object.getPrototypeOf() 方法是一樣的,都是返回指定物件的原型(即,內部的 [[Prototype]] 屬性的值)
Reflect.getPrototypeOf(target)

① ES5 用法

const d = New Date()

Object.getPrototypeOf(d)
// {constructor: ƒ, toString: ƒ, toDateString: ƒ, toTimeString: ƒ, toISOString: ƒ, …}

② ES6 用法

const d = New Date()

Reflect.getPrototypeOf(d)
// {constructor: ƒ, toString: ƒ, toDateString: ƒ, toTimeString: ƒ, toISOString: ƒ, …}

10、.has()

判斷一個物件是否存在某個屬性,和 in 運算子 的功能完全相同
Reflect.has(target, propertyKey)

const obj = { x: 1, y: 2 }

Reflect.has(obj, 'x')  // true
Reflect.has(obj, 'z')  // false

11、.isExtensible()

判斷一個物件是否可擴充套件
Reflect.isExtensibleObject.isExtensible 方法一樣,都是判斷一個物件是否可擴充套件 (即是否能夠新增新的屬性)
Reflect.isExtensible(target)

const obj = { x: 1, y: 2 }

Reflect.isExtensible(obj)  // true

Object.freeze(obj)  // 阻止新屬性新增到物件
obj.z = 3

Reflect.isExtensible(obj)  // false
obj  // {x: 1, y: 2}

12、.ownKeys()

判斷物件自身屬性
Reflect.ownKeys 方法返回一個由目標物件自身的屬性鍵組成的陣列,它的返回值等同於 `Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
Reflect.ownKeys(target)

const obj = { x: 1, y: 2 }

Reflect.ownKeys(obj)  // ["x", "y"]
Reflect.ownKeys([])  // ["length"]
Reflect.ownKeys([1, 2])  // ["0", "1", "length"]

13、.preventExtensions()

阻止新屬性新增到物件,等同於Object.freeze()
Reflect.preventExtensions 方法阻止新屬性新增到物件,例如:防止將來對物件的擴充套件被新增到物件中,與 Object.preventExtensions() 方法一致

Reflect.preventExtensions(target)

const obj = { x: 1, y: 2 }

Reflect.isExtensible(obj)  // true

Reflect.preventExtensions(obj)  // 阻止新屬性新增到物件
obj.z = 3

Reflect.isExtensible(obj)  // false
obj  // {x: 1, y: 2}

14、.set()

寫資料
Reflect.set 方法允許你在物件上設定屬性,用來給屬性賦值,類似 property accessor 的語法,但它是以函式的方式
Reflect.set(target, propertyKey, value[, receiver])

const obj = { x: 1, y: 2 }
Reflect.set(obj, 'z', 4)

obj  // {x: 1, y: 2, z: 4}

const arr = ['apple', 'pear']
Reflect.set(arr, 1, 'banana')

arr  // ["apple", "banana"]

15、.setPrototypeOf()

Reflect.setPrototypeOf 方法改變指定物件的原型 (即內部的 [[Prototype]] 屬性值)
Reflect.setPrototypeOf(target, prototype)

const arr = ['apple', 'pear']
Reflect.getPrototypeOf(arr)
// [constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ,…]

Reflect.setPrototypeOf(arr, String.prototype)
Reflect.getPrototypeOf(arr)
// String {"", constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}

相關文章