this到底是什么
this绑定的是调用位置所在的对象:并不是调用所在的作用域或者调用函数本身。
首先来看看调用位置:
调用位置就是函数在代码中被调用的位置。要找到函数调用的位置,需要分析调用栈(就是为了到达当前执行位置所调用的所有函数),调用位置就在当前正在执行的函数的前一个调用中。
举个例子:
function baz() {
//当前调用栈是:baz
//因此,调用位置是全局作用域
console.log("baz");
bar();//<-- bar的调用位置
}
function bar() {
//当前调用栈是baz-->bar
//因此,当前调用位置在baz中
console.log("bar");
foo();//<-- foo的调用位置
}
function foo() {
//当前调用栈是baz-->bar-->foo
//因此当前调用为置在bar中
console.log("foo");
}
baz();//<--baz的调用位置
找到调用位置后,我们需要确定this的绑定对象,下面介绍this绑定的四条规则。
绑定规则
绑定规则有四种,本文介绍其中两种,剩余两种在下一篇博客中介绍。
1. 默认绑定
函数直接使用不带任何修饰的函数引用进行调用,使用默认绑定,无法应用其他规则。如果不能理解,看下面的例子:
function foo() {
console.log(this.a);
}
var a=2;
foo();
上述代码中,foo()
的调用位置是全局作用域,而且是直接进行函数引用的调用,this绑定到全局对象,this.a
被解析成全局变量a。
如果使用严格模式(strict mode),全局对象将无法使用默认绑定,this会绑定到undefined:
function foo() {
"use strict";
console.log(this);
}
foo();//undefined
还有一个重要的细节需要注意,虽然this的绑定规则取决于调用位置,但是如果函数运行在严格模式下,则this的绑定与函数的调用位置无关,一律绑定到全局对象上:
function foo() {
console.log(this.a);
}
var a=2;
function bar() {
var a=3;
foo();
}
bar();//2
2. 隐式绑定
如果函数的调用位置有上下文对象,或者说函数被某个对象拥有或包含,则使用隐式绑定。
function foo(){
console.log(this.a);
}
var obj={
a:2,
foo:foo
}
obj.foo();//2
obj对象拥有foo属性,值为foo()
函数,调用位置使用obj上下文来引用函数,隐式绑定规则会把函数调用中的this绑定到这个上下文对象obj上,这时,obj.a
和this.a
是一样的。
对象属性引用链中只有最顶层或者说最后一层会影响调用位置,举例来说:
function foo() {
console.log(this.a);
}
var obj2={
a:42,
foo:foo
}
var obj1={
a:2,
obj2:obj2
}
obj1.obj2.foo();//42
隐式丢失
一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上(取决于是否是严格模式)。
看下面的代码:
function foo(){
console.log(this.a);
}
var obj= {
a:2,
foo:foo
}
var bar=obj.foo;
var a="opps,global";
bar();//"opps,global"
虽然bar是obj.foo
的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()
其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
传入回调函数的情况:
function foo() {
console.log(this.a);
}
function doFoo(fn) {
fn();
}
var obj= {
a:2,
foo:foo
}
var a="opps,global";
doFoo(obj.foo);//"opps,global"
参数传递也是一种隐式赋值,本例将obj.foo赋值给参数fn,所以结果和上例一样。