1

事情起源于一段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

image

好吧,果然是我大 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 把原生 bindthis 作为参数传了,这不就是 call 吗?所以可以改写为

Function.prototype.bind.call(f, {}, 1, 2, 3)(4, 5, 6);

所以我们的 function bind 就是 Function.prototype.bind.call,只不过它的 thisFunction.prototype.bind。改变一个函数的 this 需要 bind,所以就有

var bind = Function.prototype.bind.call.bind(Function.prototype.bind);

Function.prototype.bind.call.bindcallthis 已经后面由 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

image


CarterLi
1.3k 声望102 粉丝