绑定例外
被忽略的this
如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则:
function foo() {
console.log(this.a );
}
var a = 2;
foo.call(null);// 2
如果需要传入null,则需要使用apply(..)来“展开”一个数组,并当做参数传入一个函数。bind(..)可以对参数进行柯里化(预先设置一些参数)。
更安全的this
总是使用null来忽略this绑定可能产生一些副作用。如果某个函数确实使用了this(比如第三方库中的一个函数),那默认绑定规则会把this绑定到全局对象(在浏览器中这个对象是window),这将导致不可预计的后果(比如修改全局对象)。
一种“更安全”的做法是传入一个特殊的对象,把this绑定到这个对象不会对你的程序产生任何副作用。由于这个对象完全是一个空对象,可以用变量名ø(这是数学中表示空集合符号的小写形式)来表示它,或者其他任何名字。
在JavaScript中创建一个空对象最简单的方法都是Object.create(null)。Object.create(null)和{}很像,但是并不会创建Object.prototype这个委托,所以它比 {}“更空”。
间接引用
有时候可能(有意或者无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。
间接引用最容易在赋值时发生:
function foo() {
console.log(this.a );
}
var a = 2;
var o = {
a:3,
};
var p = {
a:4
};
o.foo();// 3
(p.foo =o.foo)();// 2
赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo(),所以这里会应用默认绑定。注意:对于默认绑定来说,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this会被绑定到undefined,否则this会被绑定到全局对象。
软绑定
之前我们已经看到过,硬绑定这种方式可以把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;
};
}
除了软绑定之外,softBind(..)的其他原理和ES5内置的bind(..)类似。它会对指定的函数进行封装,首先检查调用时的this,如果this绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不会修改this。此外,这段代码还支持可选的柯里化(详情请查看之前和bind(..)相关的介绍)。
下面我们看看softBind是否实现了软绑定功能:
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。
this词法
ES6中介绍了一种无法使用这些规则的特殊函数类型:箭头函数。(由“胖箭头”操作符 =>定义)箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
function foo(){
// 返回一个箭头函数
return(a)=>{
//this继承自foo()
console.log(this.a );
};
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
var bar = foo.call( obj1 );
bar.call( obj2 );// 2, 不是3!
foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new也不行!)
箭头函数最常用于回调函数中,例如事件处理器或者定时器。