本文默认读者已经对this有了一定了解,如果读者觉得自己对this定义或者this的简单指向还有疑问,请先看我的另一篇文章。
this直译过来就是这个,它会被自动定义到所有函数的作用域中,不过这也造成了很多困扰,因为经常很难分辨它到底代表着什么,但由于它自身的功能确实可以提供很多便利,所以程序员不管能不能掌握this,不管会经常报错,还是会乐此不彼的使用它。
那么this到底是什么?其实它是函数在运行时根据一定规则绑定到变量上的,在函数被调动时,会创建一个调用的记录,也被称为执行上下文,这个记录中记录着调用栈(函数的调用次序),函数参数,调用方式等,this是这个记录的一个属性。
简单介绍一下四种绑定机制:默认绑定(函数调用),隐式绑定(方法调用),显式绑定(call,apply调用),new绑定(构造器调用)。这四种机制已经在另一篇文章介绍过了,这里就不再赘述了,我们今天要讨论的是这四种机制的优先级和一些特殊情况。
优先级:
毫无疑问,默认绑定是最低级别的,所以我们就不考虑它了。判断一下显式绑定和隐式绑定
var foo = function foo(){
console.log(this.a);
}
var obj1 = {
a: 1,
foo: foo
};
var obj2 = {
a:2,
foo: foo
};
obj1.foo();//1
obj2.foo();//2
obj1.foo().call(obj2);//2
obj2.foo().call(obj1);//1
所以显示绑定比隐式绑定优先级更高。
接下来判断一下new绑定和显示绑定
var foo = function foo( param ){
this.a = param;
};
var obj1 = {};
var F = foo.bind(obj1);
F(1);
console.log(obj1.a);//1
var f = new F(2);
console.log(obj1.a);//1
console.log(f.a);//2
可以看出在new了一个实例后,其返回的新对象已经被改变了this绑定,但是其实原来的对象并没有被改变。所以最后的结果就是new绑定 显式绑定 隐式绑定 默认绑定。
但是我们的js总是会给我们带来无限惊喜,这些规则也并不是铁律。在使用apply、call时,我们会遇到一种场景:不需要指定this的指向,只是想利用一下它的后面传入的参数,这时我们经常会将null传入作为第一个参数,然而这会导致一个非常不好的后果,这时this其实会指向全局对象,也就是其实会使用默认规则,其实这也好理解,你把它指向设置为null,它不变成null就只能恢复默认设置了。这里我们提供一种解决方案,传入一个空的对象,这样就不会对全局对象产生影响了。
另外介绍一下软绑定,顾名思义与硬绑定相对,软绑定是指给函数绑定一个非全局变量和undefined的值,保证不会让this恢复默认绑定的同时还给以后灵活改变this绑定留有余地。下面给出软绑定的实现。
if( !Function.prototype.softBind ){//是否已经存在softBind函数
Function.prototype.softBind = function(obj){
//传入要软绑定this的对象以及参数
var fn = this;//保留原函数的引用,也就是Function
var curried = [].slice.call( arguments, 1 );//提取出传入参数的非绑定对象部分
var bound = function(){
return fn.apply(
( !this || this === (window || global )) ? obj : this,//如果this对象不为空或者不是全局对象就绑定传入的对象 this由bound的调用方式决定
curried.concat.apply( curried, arguments )//将初始参数和新参数合并
);
};
bound.prototype = Object.create( fn.prototype );//创建一个继承原函数原型的空对象,并将bound的原型设为它
return bound;
}
}
function foo(){
console.log( "name: " + this.name );
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ();// obj
obj2.foo = foo.softBind( obj );
obj2.foo();//obj2
fooOBJ.call( obj3 );//obj3
setTimeout( obj2.foo, 10 );//obj
其实代码思路很简单,就是在call、apply系列函数的基础上加了一个额外的判定功能,判定this是不是默认绑定,如果是默认绑定则启用我们软绑定的对象,其根本目的就是防止this默认绑定,也可以理解为我们手动给this设置了默认绑定。
另外还有一个需要注意的地方是es6中定义的箭头函数,他的this是直接根据外层作用域来决定的。这里就多叙述了,我将在接下来es6专题中讨论它。