跳转到内容

Reflect

Reflect 对象不仅提供了一种更加清晰和一致的方式来操作对象属性,还与 Proxy 对象紧密协作,为开发者提供了强大的元编程能力。本文阮一峰老师将深入探讨 Reflect 的静态方法、其与 Proxy 的关系以及实际应用,揭示如何在 JavaScript 中更有效地使用这些强大的工具。

概述

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有以下几个。

  1. Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上,即语言内部的方法最好从Reflect对象上拿。
  2. 修改某些方法返回的结果。如 Object.defineProperty(obj,name,desc)在无法定义属性时,会抛出一个错误,为了解决错误不得不用 try...catch 捕获;而Reflect.defineProperty(obj,name,desc)则会返回false,只需要 if...else 判断。
  3. Object 操作变成函数行为。某些Object操作是命令式,比如 name in objdelete obj[name],而 Reflect.has(obj,name)Reflect.deleteProperty(obj,name) 让它们变成了函数行为。
  4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

静态方法

Reflect 对象一共有13个静态方法。

Reflect.get(target,name,receiver)

Reflect.get方法查找并返回target对象的name属性键值,如果没有该属性,则返回undefined。接受三个参数,target对象,name属性名,receiver可选参数。

js
let obj1 = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  }
}

Reflect.get(obj1,'foo') // 1
Reflect.get(obj1,'bar') // 2
Reflect.get(obj1,'baz') // 3

如果target对象中指定了getterReflect.get会以receiver为上下文调用getter函数。

js
let obj1 = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  }
}

let obj2 = {
  foo: 4,
  bar: 5
}

Reflect.get(obj1,'baz', obj2) // 9

如果 target 参数不是一个对象,Reflect.get 会抛出一个 TypeError 异常错误。

Reflect.set(target,name,value,receiver)

Reflect.set方法给 target 对象的 name 属性赋值 value ,如果成功则返回true,否则返回false。接受四个参数,target对象,name属性名,value属性值,receiver可选参数。

js
let obj = {
  foo: 1
}
Reflect.set(obj,'foo',2)
obj.foo // 2

如果 target 对象中指定了 setterReflect.set 会以 receiver 为上下文调用 setter 函数。

js
let obj = {
  foo: 1,
  set foo(val) {
    this.foo = val + 1;
  }
}
let data = {
  foo: 4
}
Reflect.set(obj,'foo',2,data)
data.foo // 3
obj.foo // 1

如果 target 参数不是一个对象,Reflect.set 会抛出一个 TypeError 异常错误。

⚠ 注意

Reflect.set 会触发 Proxy.defineProperty 的拦截。

Reflect.has(target,name)

Reflect.has方法对应 name in obj,用来判断对象是否具有某个属性,如果具有则返回 true,否则返回 false。接受两个参数,target对象,name属性名。

js
let obj = {
  foo: 1
}
Reflect.has(obj,'foo') // true
Reflect.has(obj,'bar') // false

如果 target 参数不是一个对象,Reflect.has 会抛出一个 TypeError 异常错误。

Reflect.deleteProperty(target,name)

Reflect.deleteProperty方法用于删除对象的属性,返回一个布尔值。如果成功删除或者要删除的属性不存在,则返回 true,如果删除失败或者删除的属性依旧存在,则返回 false。接受两个参数,target对象,name属性名。

js
let obj = {
  foo: 1
}

// 旧写法
delete obj.foo // true

// 新写法
Reflect.deleteProperty(obj,'foo') // true
Reflect.deleteProperty(obj,'bar') // true

Reflect.construct(target, args)

Reflect.construct方法对应new target(...args),用来新建一个实例对象。接受两个参数,target对象,args数组。

js
function F(){
  this.foo = 'bar'
}

// 旧写法
let instance = new F()

// 新写法
let instance = Reflect.construct(F,[])

Reflect.getPrototypeOf(target)

Reflect.getPrototypeOf 方法用于读取对象的 __proto__ 属性,对应 Object.getPrototypeOf(obj)。接受一个参数,target 对象。

js
let obj = {}
Reflect.getPrototypeOf(obj) === Object.prototype // true
Reflect.getPrototypeOf(obj) === obj.__proto__ // true

Reflect.getPrototypeOfObject.getPrototypeOf 二者的区别是,如果参数不是对象,Object.getPrototypeOf 会将参数转为对象,然后再运行,而 Reflect.getPrototypeOf 会报错。

Reflect.setPrototypeOf(target,prototype)

Reflect.setPrototypeOf 方法用于设置对象的 __proto__ 属性,对应 Object.setPrototypeOf(obj, newProto)。接受两个参数,target 对象,prototype 对象。

js
let obj = {}
Object.setPrototypeOf(obj,Object.prototype) // 旧写法
Reflect.setPrototypeOf(obj,Object.prototype) // 新写法

如果 target 参数不是对象,Object.setPrototypeOf 会返回第一个参数本身,Reflect.setPrototypeOf 会报错。如果 target 参数是 undefined 或者 null,两者都会抛出报错。

Reflect.apply(target,thisArg,args)

Reflect.apply 方法等同于 Function.prototype.apply.call(func,thisArg,args),用于绑定 this 对象后执行函数。接受三个参数,target 对象,thisArg 对象,args 数组。

js
let doSomething = function (foo,bar){
  console.log('foo:',foo)
  console.log('bar:',bar)
}
Reflect.apply(doSomething,null,[1,2])
// foo: 1
// bar: 2

Reflect.defineProperty(target,name,desc)

Reflect.defineProperty 方法基本等同于 Object.defineProperty,用来为对象定义属性。接受三个参数,target 对象,name 属性名,desc 属性描述符。

js
function myReflactDefineProperty(target,name,desc){
  // 某些操作
  return Reflect.defineProperty(target,name,desc)
}

如果 target 参数不是对象,方法会抛出一个 TypeError 异常错误。

Reflect.getOwnPropertyDescriptor(target,name)

Reflect.getOwnPropertyDescriptor 方法基本等同于 Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象,将来会替代掉 Object.getOwnPropertyDescriptor 方法。接受两个参数,target 对象,name 属性名。

js
const obj = {
  foo: 1
}
Reflect.getOwnPropertyDescriptor(obj,'foo')
// {value: 1, writable: true, enumerable: true, configurable: true}

二者的区别在于,如果 target 参数不是对象,Object.getOwnPropertyDescriptor 会返回 undefined ,而 Reflect.getOwnPropertyDescriptor 会报错,表示参数非法。

Reflect.isExtensible(target)

Reflect.isExtensible 方法对应 Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展。接受一个参数,target 对象。

js
const obj = {}
Object.isExtensible(obj) // true
Reflect.isExtensible(obj) // true

二者的区别在于,如果参数不是对象,Object.isExtensible 会返回 false,而 Reflect.isExtensible 会报错,表示参数非法。

Reflect.preventExtensions(target)

Reflect.preventExtensions 方法对应 Object.preventExtensions,用于让一个对象变为不可扩展。接受一个参数,target 对象。

js
const obj = {}
Object.preventExtensions(obj) // object {}
Reflect.isExtensible(obj) // true

二者的区别在于,如果参数不是对象,ES5环境下的 Object.preventExtensions 会报错,ES6环境下会返回原对象;而 Reflect.preventExtensions 会报错。

Reflect.ownKeys(target)

Reflect.ownKeys 方法返回对象的所有属性,基本等同于 Object.getOwnPropertyNamesObject.getOwnPropertySymbols 之和。

js
const obj = {
  foo: 'bar',
  [Symbol.for('baz')]: 42
}
Reflect.ownKeys(obj)
// ["foo", Symbol(baz)]

实例运用

下面是 Reflect 对象与 Proxy 对象结合使用的一个例子。实现观察者模式。

js
const queuedObservers = new Set()

const observe = fn => queuedObservers.add(fn)
const observable = obj => new Proxy(obj,{
  set(target,key,value,receiver){
    const result = Reflect.set(target,key,value,receiver)
    queuedObservers.forEach(observer => observer())
    return result
  }
})

const person = observable({
  name: '张三',
  age: 20
})

function print(){
  console.log(`${person.name}, ${person.age}岁`)
}
observe(print)
person.name = '李四'
// 李四, 20岁
person.age = 30
// 李四, 30岁

总结

Reflect对象是 ES6 引入的新 API,用于操作对象。Reflect 对象有以下几个设计目的:

  1. Object对象的一些内部方法转移到 Reflect 对象上
  2. 修改某些方法的返回结果,使其更加友好
  3. Object操作转化为函数行为
  4. Proxy对象的方法一一对应,方便Proxy调用对应的Reflect方法。

Reflect对象提供了13个静态方法,包括Reflect.getReflect.setReflect.hasReflect.deleteProperty等,这些方法分别对应于对象属性的读取、设置、存在性检查和删除。此外,还有Reflect.construct用于构造函数的调用,Reflect.getPrototypeOfReflect.setPrototypeOf用于读取和设置对象的原型,Reflect.apply用于函数的apply操作,Reflect.definePropertyReflect.getOwnPropertyDescriptor用于定义和获取对象属性的描述符。Reflect.isExtensibleReflect.preventExtensions用于检查和阻止对象的扩展,而Reflect.ownKeys返回对象的所有属性。

最后,通过一个实例展示了如何将ReflectProxy结合使用,实现观察者模式,即当对象属性发生变化时,自动通知所有观察者。这个例子中,observable函数返回一个Proxy对象,当对象属性被设置时,会触发所有注册的观察者函数执行。