this在javascript中是比较特别的关键字,被自动定义在所有的函数作用域中,不过对它的理解确实让我煞费苦心···在这里总结下我对js中this的理解。
我们为什么要使用this呢?简单来说,this可以隐式的传递一个对象的引用,因此可以将API设计的更加简洁并且易于使用。要是没有this 我们必须显示的给每个函数传递一个上下文对象,这样会让代码变得越来越乱,而函数可以自动引用合适的上下文对象在javascript中是很重要的。
理解this首先要明白每个函数的this是在调用时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)
四条绑定规则
正如上面所说,你必须找到函数的调用位置,然后应用下面的四条规则来判断。
默认绑定
这个是最常用的函数调用类型,也就是独立函数的调用,其他规则不适用时就会用默认规则。来看下代码:
function foo(){
console.log(this.a);
}
var a=2;
foo(); //2
可以看到全局调用foo()后this.a被解析成了全局变量上的a,这就是默认绑定,this也就是指向了全局对象。
隐式绑定
接下来要考虑函数调用位置是否有上下文对象,或者说被某个对象所拥有,来看下面的代码
function foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo
};
obj.foo();//2
可以看到我们把foo当做引用属性添加到了obj中,当函数引用有上下文对象时,隐式绑定规则就会把函数调用中的this绑定到这个上下文对象,其中的this.a和obj.a 是一样的。
不过隐式绑定有有一种现象就是隐式丢失,导致最后绑定到了默认规则上,来看如下代码
function foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo
};
var bar=obj.foo;
var a=1;
bar(); //1
可以看到最后被绑定到了全局对象上的a变量上去了,以上bar引用的是foo函数的本身,因此被应用了默认绑定。值得一提的是若是传入回调函数也会发生上面的隐式丢失,例如setTimeout(obj.foo,1000)
执行结果也会是1。this的改变总是出乎人意料。在显式绑定中会介绍如何固定this修复这个问题。
显式绑定
在上面的隐式绑定中,我们可以看到每次都要在一个对象内部包含一个指向函数的属性。并通过这个属性间接引用函数。显示绑定会使用call()和apply()两个方法,这两个方法第一个参数是一样的,都是this所指向的对象,差别在第二个参数,call必须把传入函数的参数一个个写出来,apply支持参数数组形式或者arguments关键词,不过在这里第二个参数我们不必深究,使用哪个都一样。来看下代码:
function foo(){
console.log(this.a);
}
var obj={
a:2
};
foo.call(obj);//2
通过call方法我们可以在调用foo时强制把它的this绑定到obj上。不过这个还是不能解决我们前面说的隐式丢失问题,不过硬绑定可以解决,来看代码
function foo(){
console.log(this.a);
}
var obj={
a:2
};
var bar=function(){
foo.call(obj);
};
bar();//2
var a=1;
setTimeout(bar,1000);//2
可以看到解决问题了,我们看下是怎么解决得,函数bar内部手动调用了foo.call(obj),无论之后如何调用函数bar,它总会手动在obj上调用foo,这就是显示的一种强制绑定。
new绑定
直接看代码
function foo(a){
this.a=a;
}
var bar =new foo(2);
console.log(bar.a);//2
要说明的是在函数调用之前加个new关键词,就说发生了构造调用,在这之前foo只是一个普通的函数而已,不要与Java中的构造函数混淆。那么发生构造调用有什么特别之处呢,它会自动执行下面的操作:
1. 创建一个全新的对象。
2. 新对象会被执行原型链接。
3. 这个新对象会被绑定到函数调用的this。
4. 如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个新对象。
以上就是this绑定的规则,当然,凡事总有例外,有些情况下this的绑定行为会出乎意料。
另外ES6中的箭头函数是根据外层作用域来决定this的。其实和我们常常使用的self=this
机制一样。