首先,举个简单的例子,下面代码的运行结果是什么?
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
变量稍有研究的都知道,可以采用call
或apply
方法来改变this
变量的指向,例如下面的代码:
getX.call(module);
getX.apply(module, []);
上面的方法尽管可行,但是比较繁琐,每一次运行都需要额外的call
或apply
调用,并且函数是立即执行的。
bind
方法与call
和apply
一样,用于改变执行上下文(运行时this的指向),但是函数使用bind
绑定后可以稍后执行,其实bind函数的实现需要依赖apply。
首先一个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);
};
};