Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler 的方法相同。
Reflect 不是一个函数对象,因此它是不可构造的。
Reflect 是可简化 Proxy 的创建。
对于每个可被 Proxy 捕获的内部方法,在 Reflect 中都有一个对应的方法,其名称和参数与 Proxy 捕捉器相同。
因此,Reflect 可以解决 Proxy 中内置对象的局限性(如Map
,Set
,Date
,Promise
等的内建方法。)
Reflect 的主要特点:
- 不是构造函数:不能通过
new
运算符进行实例化,也不能将其当作函数来调用。它的所有属性和方法都是静态的。 - 与 Proxy 方法对应:Reflect 对象的方法与 Proxy 对象的方法一一对应,这使得在使用 Proxy 进行对象代理时,可以方便地调用 Reflect 方法来执行默认行为。
- 提供默认行为:可以用于获取对象操作的默认行为,方便进行自定义的拦截和处理。
Reflect 对象包含以下静态方法:
Reflect.apply(target, thisArg, args)
:类似于Function.prototype.apply()
方法,用于调用一个函数,并可以指定函数的this
值和参数列表。Reflect.construct(target, args, newTarget)
:类似于使用new
操作符创建实例对象,相当于执行new target(...args)
,还可以指定不同的原型对象。Reflect.defineProperty(target, propertyKey, descriptor)
:与Object.defineProperty()
类似,用于定义或修改对象的属性。如果设置成功,返回true
。Reflect.deleteProperty(target, propertyKey)
:类似于delete
操作符,用于删除对象上的属性,并返回一个布尔值表示是否成功删除。Reflect.get(target, propertyKey, receiver)
:获取对象上的属性值,类似于target[propertyKey]
,如果属性存在读取函数(getter
),则receiver
会作为getter
函数的this
值。Reflect.getOwnPropertyDescriptor(target, propertyKey)
:类似于Object.getOwnPropertyDescriptor()
,返回对象自身属性的描述符,如果不存在则返回undefined
。Reflect.getPrototypeOf(target)
:类似于Object.getPrototypeOf()
,获取对象的原型。Reflect.has(target, propertyKey)
:判断对象是否具有某个属性,与 in 运算符的功能相同,返回一个布尔值。Reflect.isExtensible(target)
:类似于Object.isExtensible()
,判断对象是否可扩展。Reflect.ownKeys(target)
:返回一个包含对象自身所有属性(不包含继承属性)的数组,类似于Object.keys()
,但不会受enumerable
影响。Reflect.preventExtensions(target)
:类似于Object.preventExtensions()
,用于阻止对象扩展,并返回一个布尔值表示操作是否成功。Reflect.set(target, propertyKey, value, receiver)
:设置对象的属性值,类似于target[propertyKey] = value
,如果设置成功,返回true
。它也会考虑属性的设置器(setter
)。如果属性存在设置器函数,receiver
会作为setter
函数的this
值。Reflect.setPrototypeOf(target, prototype)
:设置对象的原型,类似于Object.setPrototypeOf()
,如果设置成功,返回true
。
为什么 get/set 有第三个参数 receiver?
示例:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return target[prop]; // target = user
}
});
// admin 从 user 继承
let admin = {
__proto__: userProxy,
_name: "Admin"
};
// 期望输出:Admin
console.log(admin.name); // 实际输出:Guest
为什么console.log(admin.name)
实际输出的是Guest
?
如果不使用代理,console.log(admin.name)
会输出正确值Admin
。
那问题就出在代理中:
- 当我们读取
admin.name
时,由于admin
对象自身没有name
属性,搜索将转到其原型。 admin
的原型是userProxy
。
从代理读取name
属性时,get
捕捉器会被触发,并从原始对象返回target[prop]
属性。- 当调用
target[prop]
时,若prop
是一个getter
,它将在this=target
上下文中运行其代码。因此,结果是来自原始对象target
的this._name
,即来自user
。
get
捕捉器的第三个参数 receiver
的说明:如果target
对象中指定了getter
,receiver
则为getter
调用时的this
值。
receiver
保证将正确的 this
传递给 getter
。
在上面的例子中,正确的this
是 admin
。但是在调用 target[prop]
时,target
是admin
。
如何把上下文传递给 getter
?注意 getter
不能“被调用”,只能被访问。
使用Reflect.get
。
正确示例:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
// 在读取admin.name时,receiver = admin
return Reflect.get(target, prop, receiver); // (*)
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
alert(admin.name); // Admin
get
捕捉器中使用 Reflect.get(target, prop, receiver)
来获取属性,receiver
保留了对正确 this
的引用(即 admin
),通过 Reflect.get
传递给 getter
,从而解决了之前直接使用 target(prop)
导致的 this
指向问题。