zxhfighter
11/21/2013 - 8:50 AM

关于JavaScript中的bind函数

关于JavaScript中的bind函数

关于JavaScript中的bind函数

为什么需要bind函数?


首先,举个简单的例子,下面代码的运行结果是什么?

var x = 9;
var module = {
    x: 81,
    getX: function() { return this.x; }
};

module.getX(); // 问题一:这里返回多少?

var getX = module.getX;
getX(); // 问题二:这里返回多少?

这个并不难,答案是81和9,原因在于将module.getX赋值给getX变量后,this由于是迟绑定的(运行时绑定)的,运行getX()时,this指向了全局window对象(这里假定在浏览器端)。

this变量稍有研究的都知道,可以采用callapply方法来改变this变量的指向,例如下面的代码:

getX.call(module);
getX.apply(module, []);

上面的方法尽管可行,但是比较繁琐,每一次运行都需要额外的callapply调用,并且函数是立即执行的。

bind方法与callapply一样,用于改变执行上下文(运行时this的指向),但是函数使用bind绑定后可以稍后执行,其实bind函数的实现需要依赖apply。

怎样实现一个bind函数?


首先一个bind函数的语法看起来应该是这样的:

fun.bind(thisArg[, arg1[, arg2[, ...]]])

// or

util.bind(fun, thisArg[, arg1[, arg2[, ...]]])

首先需要说明的是在ES5中,已经实现了原生bind函数,为了兼容之前版本,可以编写一段polyfill代码(此种方式调试不是特别方便),可以看到bind函数的实现利用闭包原理,保存下来了this的指向:

if (!Function.prototype.bind) {
    Function.prototype.bind = function(oThis) {
        if (typeof this !== 'function' ) {
            throw new TypeError('Function.prototype.bind - variable is not callable');
        }
        
        var aArgs = Array.prototype.slice.call(arguments, 1);
        var fToBind = this;
        var fNop = function() {};
        var fBound = function() {
            return fToBind.apply(this instanceof fNop && oThis 
                                      ? this
                                      : oThis || window,
                                      aArgs.concat(Array.prototype.slice.call(arguments)));
        };
        
        fNop.prototype = this.prototype;
        fBound.prototype = new FNop();
        
        return fBound;
    };
}

最终实现


这是ER3的代码实现:

// `bind`的实现特别使用引擎原生的,
// 因为自己实现的`bind`很会影响调试时的单步调试,
// 跳进一个函数的时候还要经过这个`bind`几步很烦,原生的就不会
var nativeBind = Function.prototype.bind;
/**
 * 固定函数的`this`变量和若干参数
 *
 * @param {function} fn 操作的目标函数
 * @param {*} context 函数的`this`变量
 * @param {...*} args 固定的参数
 * @return {function} 固定了`this`变量和若干参数后的新函数对象
 */
util.bind = nativeBind
    ? function (fn) {
        return nativeBind.apply(fn, [].slice.call(arguments, 1));
    }
    : function (fn, context) {
        var extraArgs = [].slice.call(arguments, 2);
        return function () {
            var args = extraArgs.concat([].slice.call(arguments));
            return fn.apply(context, args);
        };
    };