“这是我参与更文挑战的第7天,活动详情查看: 更文挑战”
上文一个Javascript 代理 Proxy 的成长之旅,我们简单认识了一下 Proxy
的使用方法,本文我们学习一下 Proxy
的好朋友 Reflect
。
初识Reflect
Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers. Reflect is not a function object, so it’s not constructible.
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
主要特征:
- 不可构造,不能使用 new 进行调用
- 所有方法和 Proxy handlers 相同
- 所有的方法都是静态方法,类似于
Math
- 很多方法和
Ojbect
相同,但行为略微有所区别。譬如Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。
静态方法列表:
Reflect对象一共有 13 个静态方法(匹配Proxy的13种拦截行为)。
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.ownKeys(target)
- …
具体参见 MDN 文档-Reflect
示例用法
- 独立使用 Reflect.get + Reflect.has + Reflect.ownKeys + Reflect.set
const duck = {
name: 'Maurice',
color: 'white',
greeting: function() {
console.log(`Quaaaack! My name is ${this.name}`);
}
}
Reflect.get(duck, 'name'); // => "Maurice"
Reflect.has(duck, 'color'); // => true
Reflect.has(duck, 'haircut'); // => false
Reflect.ownKeys(duck); // => [ "name", "color", "greeting" ]
Reflect.set(duck, 'eyes', 'black');
console.log(duck.eyes); // => black
复制代码
- Proxy 内的 Reflect.get + Reflect.has
let hero = {
name: '张三',
}
let handler = {
get(target, name) {
return Reflect.get(target, name);
},
,
has(target, name) {
return Reflect.has(target, name);
}
}
let heroProxy = new Proxy(hero, handler);
console.log(heroProxy.name); // => 张三
console.log('name' in heroProxy); // => true
复制代码
简写 Reflect 的调用
在 Proxy handler 内部, 因为所有的参数都一致,所以我们可以用...arguments
简化调用。
...
const handler = {
get(target, prop, receiver) {
return Reflect.get(...arguments);
}
}
...
复制代码
为什么需要 Reflect
正确的上下文引用
在上面的案例中,我们其实换传统方法实现,是完全木有问题的,具体如下:
let hero = {
name: '张三',
}
let handler = {
get(target, name) {
return target[name];
// return Reflect.get(target, name);
},
has(target, name) {
return name in target;
// return Reflect.has(target, name);
}
}
let heroProxy = new Proxy(hero, handler);
console.log(heroProxy.name); // => 张三
console.log('name' in heroProxy); // => true
复制代码
那什么时候需要 Reflect
呢。我们先看一个复杂的例子。
let user = {
_name: "张三",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return Reflect.get(target, prop);
// return target[prop]; // (*) target = user
}
});
let admin = {
__proto__: userProxy,
_name: "李四"
};
// 期待 『李四』,却输出了 『张三』(?!?)
console.log(admin.name); // => 张三
复制代码
上述的情况可以看到无论是使用 Reflect.get(target, prop)
还是 target[prop]
, 都是张三
。很神奇的表现,对吗。那我们该怎么才能按预期的输出李四
呢。
如何在这种情况下,正确的传递上下文,是个问题。如果是普通的函数的话,我们还可以通过 call/apply
,但在这里我们是 getter
,而不是调用。
优秀的你一定看到了 get(target, prop, receiver)
有第三个参数没有使用,而且我们也知道 Reflect 的方法的参数是和 Proxy handler 一致的,那我们试试将receiver
传递进入Reflect.get
,看修改以后的效果。
let user = {
_name: "张三",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) { // receiver = admin
return Reflect.get(target, prop, receiver);
}
});
let admin = {
__proto__: userProxy,
_name: "李四"
};
console.log(admin.name); // => 李四
复制代码
It is OK now。 开心。可以看到第三个参数 receiver
保持了正确的 this
引用,在我们的示例中,指向了 admin
。
如果没有 Reflect.get
这个 API(配合第三个参数)的话,我们就将束手无策了。
在复杂的使用场景保持正确的上下文,这是 Reflect 一系列 API的一个重要意义所在。
和 Object 的方法略有区别,丰富使用场景
和 Object 的一系列方法略微有所差异,丰富了使用场景,这是第二个意义。
区别具体参见 比较 Reflect 和 Object 方法
正因为有所区别,所以我们可以根据具体的场景使用相应的 API 。
譬如 defineProperty
, 因为 Object.defineProperty
可能抛错,那么在不希望 catch 的场景,那就只能用 Reflect.defineProperty
。
感谢
通过以上,我想我们大致理解学习了 Reflect
的基础并了解了它的使用场景了。
本文参考或引用了以下文章,一并致谢。
参考: