天天看點

JavaScript 反射機制及 Reflect 詳解

JavaScript 反射機制及 Reflect 詳解

一、什麼是反射機制

反射機制是在編譯階段不知道是哪個類被加載,而是在運作的時候才加載、執行。

也就是說,反射機制指的是程式在運作時能夠擷取自身的資訊。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 就統一了操作方式

JavaScript 反射機制及 Reflect 詳解

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"isnotnon-nullobject


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.isExtensible 與 Object.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()

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: ƒ, …}