事情起源于一段JS代码:
function bind(func, context) {
var args = nativeSlice.call(arguments, 2);
return function () {
return func.apply(context, args.concat(nativeSlice.call(arguments)));
};
}
源代码出自 echarts 的底层依赖 zrender,为了优化我的 offscreen-echarts 库我在读 echarts 的源码,于是就发现了这段代码。
干嘛要自己重新实现一遍 bind
,难道还有浏览器不支持?于是去查了一下 MDN:
好吧,果然是我大 IE8。想到 echarts 里大量的 VML 代码,我大百度还是对 IE8 用户这类濒危物种放心不下啊。但是看这个 bind 实现又是 apply 又是 concat 又是 slice 又是 call 的,性能肯定好不到哪去。
但是干嘛非要让我们这些 Chrome 用户为 IE 这种糟粕付出代价呢?
不行,非得改改不可(重度强迫症患者)。然而代码都已经用了 bind(f, xxx)
而不是 f.bind(xxx)
。function bind
是逃不掉了,你只能用原生的 Function.prototype.bind
去优化这个 function bind
仔细观察这个 function bind
的用法
bind(f, {}, 1, 2, 3)(4, 5, 6);
与原生 Function.prototype.bind
用法
f.bind({}, 1, 2, 3)(4,5,6);
的区别,function bind
把原生 bind
的 this
作为参数传了,这不就是 call
吗?所以可以改写为
Function.prototype.bind.call(f, {}, 1, 2, 3)(4, 5, 6);
所以我们的 function bind
就是 Function.prototype.bind.call
,只不过它的 this
是 Function.prototype.bind
。改变一个函数的 this
需要 bind
,所以就有
var bind = Function.prototype.bind.call.bind(Function.prototype.bind);
Function.prototype.bind.call.bind
中 call
的 this
已经后面由 bind
指定了,call
前面的 this
已经失去了意义,所以其等价于
var bind = Function.prototype.call.bind(Function.prototype.bind);
我们需要在浏览器支持原生 bind
的前提下用新实现覆盖原始的 function bind
,所以改写为
if (bind.bind) {
bind = bind.call.bind(bind.bind);
}
bind.bind === Function.prototype.bind
,和 [].slice === Array.prototype.slice
一个意思。
这里其实已经是最佳实现了,因为只是把原生 bind
使用 call
方法调用,性能几乎等同于原生 bind
的性能。如果把参数 bind.bind
移到函数前面再 bind
一层,就变成
if (bind.bind) {
bind = bind.bind.bind(bind.call);
}
于是就出现了 bind.bind.bind
。但是因为这里其实有两层 bind
所以实际性能是有损耗的:https://jsben.ch/sEcop
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。