了解这个之前,需要大家对this绑定有所了解。this绑定可以细分为默认绑定,隐式绑定,显式绑定(硬绑定)和new绑定四种。我们今天主要来讲硬绑定中bind的实现细节以及如何修改bind来达到更灵活的绑定应用(这里称为软绑定)
this的绑定规则
每个函数的 this 是在调用时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)。
默认绑定
独立函数在运行的时候,函数的调用是不带任何修饰的,无法应用其他规则时的默认规则即为默认绑定。在严格模式下面,默认绑定会绑定到undefined。
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo(); // TypeError: this is undefined
这里有一个微妙但是非常重要的细节,虽然
this的绑定规则完全取决于调用位置,但是只 有foo()运行在非strict mode下时,默认绑定才能绑定到全局对象;严格模式下与foo()的调用位置无关.
function foo() {
console.log( this.a );
}
var a = 2;
(function(){
"use strict";
foo(); // 2
})();
隐式绑定
隐式绑定规则是指调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。
function foo() {
console.log( this.a );
}
var obj = { a: 2, foo: foo };
obj.foo(); // 2
调用位置会使用
obj上下文来引用函数,因此你可以说函数被调用时obj对象“拥 有”或者“包含”它。无论你如何称呼这个模式,当foo()被调用时,它的落脚点确实指向obj对象。当函数引 用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用foo()时this被绑定到obj,因此this.a和obj.a是一样的。
显示绑定
可以使用函数的call(..)和apply(..)方法传入绑定对象,它们会把这个对象绑定到this,接着在调用函数时指定这个 this。因为你可以直接指定this的绑定对象,因此我们称之为显式绑定。
function foo() {
console.log( this.a );
}
var obj = { a:2 };
foo.call( obj ); // 2
如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对 象,这个原始值会被转换成它的对象形式(也就是new String(..)、new Boolean(..)或者 new Number(..))。这通常被称为“装箱”。
new 绑定
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行[[原型]]连接。
- 这个新对象会绑定到函数调用的this。
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
使用
new来调用foo(..)时,我们会构造一个新对象并把它绑定到foo(..)调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。
bind的代码实现
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== "function") {
// 与 ECMAScript 5 最接近的 // 内部 IsCallable 函数
throw new TypeError(
"Function.prototype.bind - what is trying " +
"to be bound is not callable"
);
}
var aArgs = Array.prototype.slice.call( arguments, 1 ),
fToBind = this,
fNOP = function(){},
fBound = function() {
return fToBind.apply(
(
this instanceof fNOP &&
oThis ? this : oThis
),
aArgs.concat(
Array.prototype.slice.call( arguments )
);
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
}
这里有个很有意思的点就是:
this instanceof fNOP && oThis ? this : oThis // ... 以及: fNOP.prototype = this.prototype; fBound.prototype = new fNOP();
简单来说,这段代码会判断硬绑定函数是否是被 new 调用,如果是的话就会使用新创建的 this 替换硬绑定的 this。用代码表示就是:
var obj = {val: 123}
function foo(value) {
if(value) this.val = value;
console.log(this.val)
}
var bar = foo.bind(obj);
bar() // 123
var baz = new bar("p2");
baz.val; // p2
// 原型链接
baz.__proto__ === bar.prototype === fBound.prototype === new fNOP() === fNOP.prototype
软绑定
之前我们已经看到过,硬绑定这种方式可以把 this 强制绑定到指定的对象(除了使用 new 时),防止函数调用应用默认绑定规则。问题在于,硬绑定会大大降低函数的灵活性,使 用硬绑定之后就无法使用隐式绑定或者显式绑定来修改 this。如果可以给默认绑定指定一个全局对象和 undefined 以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改 this 的能力。可以通过一种被称为软绑定的方法来实现我们想要的效果:
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
// 捕获所有 curried 参数
var curried = [].slice.call( arguments, 1 );
var bound = function() {
return fn.apply(
(!this || this === (window || global)) ?
obj : this
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
它会对指定的函数进行封装,首先检查调用时的
this,如果this绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不会修改this。
function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call( obj3 ); // name: obj3 <---- 看!
setTimeout( obj2.foo, 10 ); // name: obj <---- 应用了软绑定
可以看到,软绑定版本的
foo()可以手动将this绑定到obj2或者obj3上,但如果应用默认绑定,则会将this绑定到obj。
到这里就结束了,希望对你有些帮助。
参考:《你不知到的javaScript》