前言

如果面试官让你用一句话总结this的指向,你会怎么总结。广泛流传的一句话是:

谁调用它,this就指向谁。

严格来说,这句话的描述不够规范也不完全准确。对this指向的分析离不开执行上下文。因此,本文我们将结合执行上下文对this的各种使用场景进行分析。

全局环境下的this

例题一

1
2
3
4
5
6
7
8
9
function f1 () {
console.log(this)
}
function f2 () {
'use strict'
console.log(this)
}
f1() // window
f2() // undefined

函数f1f2都在全局环境下被调用或者说没有直接调用者,在非严格模式下this指向了调用它window对象,但在严格模式下this指向undefined

例题二

1
2
3
4
5
6
7
8
9
const foo = {
bar: 10,
fn: function() {
console.log(this)
console.log(this.bar)
}
}
var fn1 = foo.fn
fn1()
1
2
3
4
5
6
7
8
9
10
11
12

## 上下文对象调用中的 this
### 例题三
```javascript
const foo = {
bar: 10,
fn: function() {
console.log(this)
console.log(this.bar)
}
}
foo.fn()

在此题中,我们先在全局环境下调用foo.fn(),而函数fnfoo对象中被调用。此时,this指向的是最后调用它的对象foothis.bar就相当于foo.bar

例题四

1
2
3
4
5
6
7
8
9
10
const person = {
name: 'Lucas',
brother: {
name: 'Mike',
fn: function() {
return this.name
}
}
}
console.log(person.brother.fn())

由例题三的分析,此题的结果也很明了,最后调用函数fn的对象为brother,因此this.name就相当于brother.name

例题五

思考下面比较复杂的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const o1 = {
text: 'o1',
fn: function() {
return this
}
}
const o2 = {
text: 'o2',
fn: function() {
return o1.fn()
}
}
const o3 = {
text: 'o3',
fn: function() {
var fn = o1.fn
return fn()
}
}

console.log(o1.fn())
console.log(o2.fn())
console.log(o3.fn())

调用o1.fn()时,this指向o1对象,这个没什么好说的。
调用o2.fn()时,this也指向最后调用它的o1
调用o3.fn()时,函数fn先被赋值为函数o1.fn,然后再调用赋值后的函数fn。此时情况就还原为了例子一。

bind/call/apply 改变 this 指向

先举个简单例子

例题六

1
2
3
4
5
6
7
8
9
10
const foo = {
name: 'lucas',
logName: function() {
console.log(this.name)
}
}
const bar = {
name: 'mike'
}
console.log(foo.logName.call(bar))

利用call改变this指向,使this指向bar对象,最后打印bar.name

1
2
3
4
5
6
7
8
9
10

## 构造函数和 this
如下例题所示
### 例题七
```javascript
function Foo() {
this.bar = "Lucas"
}
const instance = new Foo()
console.log(instance.bar)

显然,对于oop比较熟悉的人立马就知道这里打印的就是this.bar的值。
new操作符调用构造函数时,会对this指向造成什么影响呢?我们简单说一下new操作符的工作就是:

  1. 创建一个新对象。
  2. 将构造函数的this指向这个新对象。
  3. 为这个对象添加属性、方法等。
  4. 最终返回新对象。

箭头函数与this

这个也是我们在使用箭头函数时要注意的问题。

例题八

1
2
3
4
5
6
7
8
const foo = {  
fn: function () {
setTimeout(function() {
console.log(this)
})
}
}
console.log(foo.fn())

这里由于产生了闭包,this指向了window,与我们原先的想法有差异。为了能让this指向调用fn函数的foo对象,我们可以使用箭头函数来实现。

1
2
3
4
5
6
7
8
const foo = {  
fn: function () {
setTimeout(() => {
console.log(this)
})
}
}
console.log(foo.fn())

此时,this指向最近的函数即fnthis指向。

this的优先级

this的绑定我们通常分为两类

  1. 显式绑定,如callapplybindnewthis的绑定。
  2. 隐式绑定,如根据调用关系确定的this指向。

那么显示绑定和隐式绑定谁的优先级更高呢?

例题九

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

const obj1 = {
a: 1,
foo: foo
}

const obj2 = {
a: 2,
foo: foo
}

obj1.foo.call(obj2)
obj2.foo.call(obj1)

上例比较了对象调用与call的优先级,根据打印的结果为2,1可以得知:callapply的显式绑定的优先级要高于隐式绑定。

例题十

1
2
3
4
5
6
7
8
9
function foo (a) {
this.a = a
}

const obj1 = {}

var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a)

上例使用bindbar函数中this绑定为obj1对象,因此执行bar(2)后,obj1.a的值为2。
此时,若将bar函数当做构造函数,new一个对象时,this将绑定到新创建的对象上。

1
2
var baz = new bar(3)
console.log(baz.a)

如上例,结果将输出3。
由此可以得到结论,同样是显示绑定new的优先级比bind高。

例题十一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function foo() {
return a => {
console.log(this.a)
};
}

const obj1 = {
a: 2
}

const obj2 = {
a: 3
}

const bar = foo.call(obj1)
console.log(bar.call(obj2))

首先用callthis绑定到obj1,此时bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。因此,最后将打印2。

后记

通过上面的例子,我们可以得知:

this的指向,是在调用函数时根据执行上下文所动态确定的。

当然,要完全解决this指向问题,光靠上面几个例子是不够的,还需要自己多写多看多做总结,才能把知识点融会贯通。