javascript中bind多次绑定无效——从源码角度看看bind到底做了什么

版权声明:欢迎转载,转载请注明原始出处 https://blog.csdn.net/xiaomingelv/article/details/90814810

我们都知道,js中的函数提供了一个bind方法用来绑定this,该方法会返回一个新的函数,在调用返回的函数时会设置this关键字为第一个参数的值。并在调用新函数时,将给定参数列表除第一个参数之外的其他参数作为原函数的参数序列的前若干项。

有了bind函数,我们可以很方便的绑定函数的this,但是偶尔我们也会遇到类似下面代码展示的困惑

function testBind() {
    console.log(this.test);
}

var firstBind = testBind.bind({test: 1});
var secondBind = firstBind.bind({test: 2});
var thirdBind = secondBind.bind({test: 3});
firstBind();//输出1,意料之中
secondBind();//输出1,what?绑定失败?
thirdBind();//输出1,what?绑定失败?

通过上面的示例代码我们可以发现我们通过bind绑定this,只有第一次会成功,此后不管再绑定多少次都只会保留第一次绑定,这是为什么呢,我们来看看MDN提供的这段代码,他揭示了bind函数的实现原理

if (!Function.prototype.bind) {
        Function.prototype.bind = function (oThis) {
            if (typeof this !== 'function') {
                //bind函数不是被函数调用时,检测到this不是函数,此处直接抛出错误
                throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
            }

            var aArgs = Array.prototype.slice.call(arguments, 1),//获取参数,从参数列表第一个开始获取,返回数组,获取除绑定的this之外的其他参数
                fToBind = this,//需要修改this的函数
                fNOP = function () {
                },
                fBound = function () {
                    // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
                    return fToBind.apply(this instanceof fBound
                        ? this
                        : oThis,
                        // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的,即将bind函数中的除第一个参数之外的其他参数追加到实际执行中
                        aArgs.concat(Array.prototype.slice.call(arguments)));
                };

            // 维护原型关系
            if (this.prototype) {
                // 此处获取原函数的原型,返回fBound时作为fBound的原型,维护原有的原型链
                fNOP.prototype = this.prototype;
            }
            // 下行的代码使fBound.prototype是fNOP的实例,因此
            // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
            fBound.prototype = new fNOP();

            return fBound;
        };
    }

这段代码实际上并不是bind的实现代码,它只是一个polyfill,它的作用是当浏览器不支持es5时对bind函数进行模拟,虽然跟真正的bind还是会有一定程度的不同,但基本模拟出了bind的基本功能,我们可以通过它来了解bind函数的实现原理。

由于代码已经写了非常详细的注释,这里我们只解释一下主流程。我们把最开始要进行绑定的函数称为fToBind,返回值称为fBound。从代码我们可以看到,bind执行的结果并没有返回fToBind,而是返回一个新的函数fBound,真正的待执行函数被封装在fBound里面。当我们绑定完成之后执行fBound的时候,在fBound内部fToBind会调用apply进行绑定并执行。而此时,apply的第一个参数已经是确定下来的了。接下来分为两种情况,当判断到当前函数是被当成构造函数执行时,此时忽略强行绑定,保留new操作;第二种情况是正常绑定,即获取bind函数传入的this进行绑定。

那么问题来了,为什么二次绑定的时候完全无效了,原因在于bind函数返回了一个新的函数fBoundfBound执行时,内部fToBind绑定所需要的this都已经确定下来了,当我们进行二次绑定时,操作的对象已经是fBound了,此时再进行bind操作时,我们只能改变fBound的this,但函数不作为构造函数执行时fBound内部的绑定操作并不依赖于fBound的this,而是依赖于第一次传入的oThis。所以不管我们再怎么绑定,都不能再改变绑定的结果了。

简单来说,再次bind的时候,我们已经无法对最原始的待绑定函数进行操作了,我们操作的只是它的代理。

但是,总会有但是

如果你做了以下骚操作:进行了两次bind,接着再把返回的函数当做构造函数执行,如果你看懂了上面的代码和解释,你会发现,情况又不太一样了。但是答应我,别这么骚好吗。

但是,总是会有但是

如果没看懂,留言区见吧,说出你的疑问,有说错的地方,也烦请指出

结论

1、bind操作只有第一次绑定有效果,之后再次进行绑定,不会有效果

2、new操作的绑定操作优先度要高于bind,事实上,也会高于apply和call

猜你喜欢

转载自blog.csdn.net/xiaomingelv/article/details/90814810