大家都知道 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.getElementById 和 Date.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/
大家都知道 JS 非常灵活,很多原生 API 都可以被开发者重写。
然而有时我们希望调用的 API 是原生的(至少在我们程序运行后不再有变化)。常见的做法,就是在程序运行时将原始接口进行备份:
这样,即使后续程序修改了
Date.now,我们程序内部的Date_now引用的仍是原始版本。当然这个案例比较简单。下面思考一个更复杂的,如果换成
document.getElementById,又改如何实现?也许你首先会想到这样:
但是
document.getElementById和Date.now不同,这个 API 并不是静态函数,它依赖this。直接调用 fn 的话,就会抛出Illegal invocation错误。(当然很久以前的古老 IE 浏览器可以这么调用,这里不扯远)当然你会说,把
document也进行备份,然后通过call/apply就可以了:从工程角度来看,到此确实可以了。但从理论上说,此处的
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即可。 上述案例即可这样实现:演示:http://jsfiddle.net/9m2a7fts/1/