JavaScript 反射机制及 Reflect 详解

    |     2021年6月7日   |   html/css/js   |     0 条评论   |    800

反射指编译期还不知道加载哪个类,到运行时才加载、执行;程序运行中能拿到自身信息。JS 里 apply 就是一种反射用法。

ES6 的 Reflect 把对对象的底层操作收成一套静态 API,并作为 Proxy 拦截器的默认实现。它不是构造函数,不能 new,也不能当普通函数调用。

一、什么是 Reflect

Reflect 是内建对象,用来拦截 JavaScript 操作。所有属性和方法都是静态的,内部封装了一系列对象底层操作。

若 Proxy 没写 get,默认就是 Reflect.get:

const proxy = new Proxy(obj, {
  get(target, property) {
    // 如果没有定义 get 方法,那么默认返回的就是 Reflect 的 get 方法
    return Reflect.get(target, property)
  }
})

以前改对象可以用 Object 上的方法,也可以用 in、delete 运算符。Reflect 把操作方式统一成函数调用。API 一览:

20210607200328

二、apply

Reflect.apply(target, thisArgument, argumentsList)

  • target:目标函数(必选)
  • thisArgument:调用时绑定的 this(可选)
  • argumentsList:实参列表,应是类数组(可选)

ES5

先指定方法,再 apply:

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

ES6

先交出 apply,再指定方法:

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

静态扫描时 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

三、construct

用反射创建实例,类似 new target(…args)。

Reflect.construct(target, argumentsList[, newTarget])

  • target:被运行的构造函数(必选)
  • argumentsList:参数数组或伪数组(可选)
  • newTarget:参考 new.target;省略则与 target 相同(可选)
let a = new Date()

a.getTime()  // 1632632744483
let b = Reflect.construct(Date, [])
b.getTime()  // 1632632744484

四、defineProperty / deleteProperty

Reflect.defineProperty 与 Object.defineProperty 几乎一样:Reflect.defineProperty(target, propertyKey, attributes)。

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

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

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

效果一样,都能改对象;差别在返回值:Object 返回对象本身,Reflect 返回 true。W3C 方向是逐步把 Object 上这类方法迁到 Reflect。

Reflect.deleteProperty 删除属性,返回是否成功,和非严格模式 delete 几乎相同。

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

五、get / getOwnPropertyDescriptor / getPrototypeOf

Reflect.get 像读 target[propertyKey],但是函数形式。

const obj = { x: 1, y: 2 }
obj.x  // 1
obj['x']  // 1
const obj = { x: 1, y: 2 }
Reflect.get(obj, 'x')  // 1
Reflect.get(['a', 'b', 'c'], 1)  // b

Reflect.getOwnPropertyDescriptor 类似 Object.getOwnPropertyDescriptor:有则返回描述符,无则 undefined。

const obj = { x: 1, y: 2 }
Object.getOwnPropertyDescriptor(obj, 'x')
// {value: 1, writable: true, enumerable: true, configurable: true}
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 抛 TypeError;Object 会先强制转成对象。

Reflect.getOwnPropertyDescriptor("foo", 0);
// TypeError: "foo" is not a non-null object
Object.getOwnPropertyDescriptor("foo", 0);
// { value: "f", writable: false, enumerable: true, configurable: false }

Reflect.getPrototypeOf 与 Object.getPrototypeOf 一样,返回 [[Prototype]]。

const d = new Date()
Object.getPrototypeOf(d)
// {constructor: ƒ, toString: ƒ, toDateString: ƒ, toTimeString: ƒ, toISOString: ƒ, …}
const d = new Date()
Reflect.getPrototypeOf(d)
// {constructor: ƒ, toString: ƒ, toDateString: ƒ, toTimeString: ƒ, toISOString: ƒ, …}

原文写成了 New Date(),构造函数应小写 new。

六、has / isExtensible / ownKeys

Reflect.has 判断属性是否存在,和 in 相同。

const obj = { x: 1, y: 2 }
Reflect.has(obj, 'x')  // true
Reflect.has(obj, 'z')  // false

Reflect.isExtensible 与 Object.isExtensible 一样,看能不能加新属性。

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}

Reflect.ownKeys 返回自身属性键,等价于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。

const obj = { x: 1, y: 2 }
Reflect.ownKeys(obj)  // ["x", "y"]
Reflect.ownKeys([])  // ["length"]
Reflect.ownKeys([1, 2])  // ["0", "1", "length"]

七、preventExtensions / set / setPrototypeOf

Reflect.preventExtensions 阻止再添加新属性,和 Object.preventExtensions() 一致。它不等于 Object.freeze():freeze 还会让已有属性不可写、不可配置。

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}

Reflect.set 以函数方式赋值,类似属性访问器。

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

Reflect.setPrototypeOf 改 [[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: ƒ, …}
方法 对应旧写法 差别
apply fn.apply(this, args) 函数当参数传入,运行时再选
construct new Ctor(...args) 反射创建实例
defineProperty Object.defineProperty 返回 true/false,不是对象
deleteProperty delete obj.x 返回是否删除成功
get / set 属性读写 函数形式,便于和 Proxy 对齐
has in 判断属性存在
preventExtensions Object.preventExtensions 只禁扩展,不是 freeze

原文:https://www.cnblogs.com/Leophen/p/14838608.html

一句话总结:Reflect 把对象操作收成静态函数,既是 Proxy 的默认实现,也比 Object 方法更统一、失败时更常返回布尔或抛 TypeError。

转载请注明来源:JavaScript 反射机制及 Reflect 详解
本文链接地址:https://ai.zhousir.top/?p=3259
回复 取消