Javascript中关于this值绑定的问题总结

Javascript中的this关键字向来是比较迷惑的地方。最深刻的体验就是在出现在各种面试题中的case,通过给出代码片段,写出程序片段的运行结果。如果对于Javascipt中执行环境(executing context)以及作用域链的机制较为熟悉的话,此类问题应该也不是难事。this值的绑定时机发生在函数被压入执行环境栈,真正执行之前。关于执行环境栈的相关问题可以参考上篇博文 《关于javascript中作用域与执行环境的解读》

this值绑定的几种场景

通俗的讲,this值的绑定取决于函数执行时,谁来调用该函数。具体研究可以发现,调用该函数的对象会先于该函数被压入执行环境栈,因此谁来调用该函数取决于该函数被压入执行环境栈之前栈中的对象。

在《You don’t Know JS》一书中阐述this的绑定问题时,主要解释了call-stack以及call-site的概念,即执行环境栈以及执行环境栈顶(个人理解,函数被调用的位置,何时何地被调用)。其中call-site决定了this的指向。下文中给出了如何决定this指向时的四种原则。

** 默认绑定(Default binding) **

单纯的函数调用,this指向全局对象,在严格模式下指向undefined。

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo(){
console.log(this);
}
var a = 2;
foo();// 2

// in strict mode
function foo(){
'use strict';
console.log(this);
}
var a = 2;
foo();// this is 'undefined'

** 隐示绑定(Implicit binding) **

这种方式下this的绑定主要取决于’.’操作符之前的对象。如下例所示:

1
2
3
4
5
6
7
8
9
function foo(){
console.log(this.a);

}
var obj = {
a: 2,
foo: foo
};
obj.foo();// 2

上述规则对于对象多层嵌套的情况同样适用,直接选择’.’操作符之前的的对象,如下例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo(){
console.log(this.a);

}
var obj2= {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo();// 42 (obj2.a)

在隐示绑定中存在一种情况,即函数作为对象的属性定义,并赋值给了另外一个变量,此时函数的调用容易出现混淆。此时该变量代表函数的引用,调用时为全局调用,执行的是默认绑定。如下例所示:

1
2
3
4
5
6
7
8
9
10
function foo(){
console.log(this.a);
}
var obj1= {
a: 42,
foo: foo
};
var bar = obj1.foo;
var a = 10;
bar();// 10,this代表global对象(浏览器中为window对象)

** 显示绑定(Explicit binding) **

通过call、apply或者bind的方式在函数执行时显示绑定this值。该种情况下this值的绑定取决于传入的参数。如下所示:

1.call方式绑定

1
2
3
4
5
6
7
8
function foo(){
console.log(this.a);
}

var obj = {
a: 2
};
foo.call(obj);// 2

2.bind方式绑定

1
2
3
4
5
6
7
8
9
10
function foo(something){
console.log(this.a,something);
return this.a + something;
}
var obj = {
a: 2
};
var bar = foo.bind(obj);
var b = bar(3);// 2 3
console.log(b);// 5

** new方式下的绑定(New binding) **

通过new方式调用构造函数时,this值绑定为新创建的实例。

1
2
3
4
5
function foo(a){
this.a = a;
}
var bar = new foo(2);
console.log(bar.a);// 2

如何判断this的值

this值的绑定主要取决于在调用时应用以上四种规则中哪一种。在判断this值应该如何绑定时遵循以下优先级排序:
new方式下的绑定 > 显示绑定 > 隐示绑定 > 默认绑定
因此判断this值遵循以下规则:

  • 是否是new方式调用,如果是,this值绑定为新创建的实例对象
  • 是否是显示调用,如果是,this值绑定为call、apply或bind时传入的参数,如果为传入参数为null则为默认绑定
  • 是否为隐示调用,如果是,this值绑定为’.’操作符之前的对象
  • 默认绑定,this值绑定为全局对象,Node环境下为global对象,浏览器环境下为window对象。严格模式时this为undefined

ES6箭头函数的this值问题

ES6种引入了箭头函数的概念,箭头函数本质上还是Function类型。在箭头函数中使用this时应注意,箭头函数在被调用时,this值绑定为就近的闭合作用域(enclosing scope)。如下所示:

1
2
3
4
5
6
7
8
9
function foo(){
setTimeout(() => {
console.log(this.a);
},100);
}
var obj = {
a: 2
};
foo.call(obj);// 2

箭头函数适合应用在一些回调函数中,如Ajax异步调用后的回调,setTimeout,AddEventListener中的回调等,在此类回调中应用箭头函数可以避免this的异常绑定问题,比如可以避免经常性的写var self = this;


参考文章:

《You don’t Know JS》

作者

monster1935

发布于

2017-01-05

更新于

2024-09-25

许可协议