关于this的指向

前言

在写js代码的时候,尤其是写带有面向对象编程式的代码的时候,我总是会想我这里使用的this到底指的是什么,这个问题真是困扰了我很久。虽然代码写久了会有一些基于经验的直觉感受,不过从本质出发是最直接的理解途径。

this的指向

默认情况

当在某个函数(function)中使用了this时,这个this指向的是该函数调用时所在的作用域,比如:

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

let a = 1

foo() // 会输出 1

可以看出上面的foo函数在执行的时候,实际上位于全局作用域(即window的作用域)中,所以this实际上指向了全局对象window,而全局作用域内声明的变量实际上都变成了window对象的属性,所以foo函数内部的this.a实际上就是window.a;但是需要注意的是,如果使用的为严格模式"use strict"),函数在执行时是不能指向全局对象的!

1
2
3
4
5
6
7
8
9
"use strict"

function foo(){
console.log(this.a)
}

let a = 1

foo() // TypeError: this is undefined

这时候foo函数的内部的this实际上就是undefined

函数引用

当函数被某个对象的属性所引用时,该函数属性(即指向该函数的对象属性)在执行的时候,this指向的为该属性所属的对象(需要注意的是对象属性可以为对象,也就是对象可以嵌套,因此该对象指的是直接包含该属性的那层对象);比如:

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

let obj = {
a: 'just do it',
fn: foo
}

obj.fn() // 输出 'just do it'

:当函数已经被某个对象属性所引用,然后又有其他变量或对象属性指向该对象属性,其实际上还是指向原函数;如同a → foob → a,实际上就是b → foo;不管中间经历多少个指向,最终指向的一定是原函数。比如:

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

let obj = {
a: 'just do it',
fn: foo
}

let bar = obj.fn // obj.fn实际指向foo,因此bar指向了foo,实际上相当于 bar = foo
bar() // undefined

从上面可以看出,中间经历多重指向后,实际上还是指向原函数,因此this具体指向要看执行时该函数是作为函数属性还是普通函数执行的。

直接绑定

Function.prototype具有3个特殊的方法:call()apply()bind()。这个三个函数之所以特殊,是因为它们都提供了一个参数用来绑定函数内部的this对象,因此可以利用这3个函数直接将我们想要this指向的对象与this进行绑定。

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

let obj = {
a: 'ok'
}

foo.call(obj) // 输出 'ok'

上面的obj对象利用call方法绑定到foo函数的this上,因此执行的时候this.a实际上就是obj.a

new绑定

在进行面向对象式的编程时,经常将函数作为构造器使用new来『构造』一个新对象,之前总以为new返回了函数的内部this对象,且this指向的应该就是新『构造』的实例。然而实际上这种想法是错误的,new关键词所做的无非是一个稍微特殊的函数调用:当该函数没有返回值或返回值不是对象(引用类型?)时时,new会构造一个对象,将该对象的__proto__属性连接到函数的prototype属性,并将该对象指向函数内部的this对象,然后返回该对象;若该函数有对象返回值,则直接返回该对象返回值(即此时相当于普通函数的调用而已)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1. 没有返回值的情况
function foo(){
this.a = 'nothing'
let b = '何でもない'
}

let o1 = new foo()
console.log(o1) // {a: 'nothing'}

// 2. 有对象返回值的情况
function bar(){
this.a = 'nothing'
let b = '何でもない'

return {a: b}
}

let o2 = new bar()
console.log(o2) // {a: '何でもない'}

// 3. 有返回值,但不是对象的情况
function fb(){
this.a = 'nothing'
let b = '何でもない'

return b
}

let o3 = new fb()
console.log(o3) // {a: 'nothing'}

箭头函数

ES6新增的箭头函数中使用的this跟普通定义的函数有所不同,这里的this指向的实际上是箭头函数定义时离得最近的一层外层函数作用域内的this对象;

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

let obj = {
a: 'so what'
}
let bar = foo.call(obj)
bar() // 输出 'so what'

上面通过将obj对象作为foo函数的this对象,然后返回一个箭头函数,该箭头函数在定义的时候内部的this实际上指向的就是此时foo函数内部的this对象,即obj对象。

:当有多个箭头函数嵌套时,this的指向也是在定义处一层一层往上直至最近的外层非箭头函数的内部this对象。

参考文档

  1. 《你不知道的JavaScript(上卷)》——第二章:this全面解析