Skip to content

有什么场合,必须使用 ES6 的 Reflect? #2

@EtherDream

Description

@EtherDream

大家都知道 JS 非常灵活,很多原生 API 都可以被开发者重写。

然而有时我们希望调用的 API 是原生的(至少在我们程序运行后不再有变化)。常见的做法,就是在程序运行时将原始接口进行备份:

(function(Date_now) {
  function call_later() {
    Date_now();
  }
  // ...
})(Date.now);

这样,即使后续程序修改了 Date.now,我们程序内部的 Date_now 引用的仍是原始版本。

当然这个案例比较简单。下面思考一个更复杂的,如果换成 document.getElementById,又改如何实现?
也许你首先会想到这样:

(function(fn) {
  fn('id')
  // ...
})(document.getElementById);

但是 document.getElementByIdDate.now 不同,这个 API 并不是静态函数,它依赖 this。直接调用 fn 的话,就会抛出 Illegal invocation 错误。(当然很久以前的古老 IE 浏览器可以这么调用,这里不扯远)

当然你会说,把 document 也进行备份,然后通过 call/apply 就可以了:

(function(document, fn) {
  fn.apply(document, [...]);
  // ...
})(document, document.getElementById);

从工程角度来看,到此确实可以了。但从理论上说,此处的 apply 其实并不能保证 100% 调用就是原生 document.getElementById。换言之,执行 apply 是有可能存在副作用的!

仔细想想,所谓的 apply 其实就是 Function 类的一个方法而已,即 Function.prototype.apply。如果把它重写了,那么 fn.apply() 就是调用重写后的函数!

所以,调用 call/apply 是无法保证绝对可靠的。

那么,能否把原生的 apply 也备份起来呢?可以。但是为了调用原生的 apply,你仍得使用 apply,于是陷入一个死循环。。。

为了解决这个窘境,是时候派上 Reflect 了。Reflect 提供了一个 apply 方法,它比 Function.prototype.apply 更底层,所以不会受到 Function 重写的影响。

更好的是,Reflect 提供的函数都是静态的,如同之前提到的 Date.now 一样!

因此,我们只需备份 Reflect.apply 即可。 上述案例即可这样实现:

(function(apply, document, fn) {
  apply(fn, document, ...);
  // ...
})(Reflect.apply, document, document.getElementById);

演示:http://jsfiddle.net/9m2a7fts/1/

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions