# 关于 this 的解释说明

我们经常在 JavaScript 中看到 this 关键字,它其实是一个对象,它可以是全局对象、当前对象,或者任意对象,函数的调用方式决定了 this 的值。接下来我们来看看不同的调用方式对 this 值的影响。

# 全局对象

在全局环境中,this 指向全局对象,在浏览器中,它就是 window 对象。下面的示例中,无论是否是在严格模式下,this 都是指向全局对象。

var x = 1
console.log(this.x)               // 1
console.log(this.x === x)         // true
console.log(this === window)      // true
1
2
3
4

如果函数是在全局环境中被调用,在非严格模式下,函数中 this 也指向全局对象;如果是在严格模式下,this 将会是 undefined。ES5 为了使 JavaScript 运行在更有限制性的环境而添加了严格模式,严格模式为了消除安全隐患,禁止了 this 关键字指向全局对象。

var x = 1
function demo() {
  console.log(this.x)
}

demo()        // 1
1
2
3
4
5
6
"use strict" // 使用严格模式
var x = 1

function demo() {
  console.log(this.x)
}

demo()        // 报错 "Cannot read property 'x' of undefined",因为此时 this 是 undefined
1
2
3
4
5
6
7
8

# 作为对象的方法调用

当函数作为某个对象的方法调用时,this 指向该对象。值得注意的是,如果将函数赋值给某个变量,并没有立即执行,this 的值就要根据函数执行时所在的环境对象进行判断。

var x = 1
var obj = {
  x: 2,
  getX: function() {
    console.log(this.x)
  }
}

obj.getX()     // 2
var a = obj.getX
a()            // 1
1
2
3
4
5
6
7
8
9
10
11

在上面的例子中,直接运行 obj.getX() ,调用该函数的对象是 obj,所以 this 指向 obj,得到 this.x 的值是 2;之后我们将 getX 方法首先赋值给变量 a,a 运行在全局环境中,所以此时 this 指向全局变量,得到 this.x 为 1。

我们再来看一个例子,如果函数被多个对象嵌套调用,this 会指向什么。

var x = 1
var obj = {
  x: 2,
  y: {
    x: 3,
    getX: function() {
      console.log(this.x)
    }
  }
}

obj.y.getX()      // 3
1
2
3
4
5
6
7
8
9
10
11
12

为什么结果是 3 而不是 2 呢,虽然 obj 是函数调用的发起者,但是 this 始终会指向直接调用函数的上一级对象,即 y。

对象可以嵌套,函数也可以,如果函数嵌套,this 会有变化吗?我们通过代码来探讨一下。

var obj= {
  y: function() {
    console.log(this === obj)
    getX()
    function getX() {
      console.log(this === obj)
      console.log(this)
    }
  }
}

obj.y()  
// true
// false
// 全局对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

在函数 y 中,this 指向了调用它的对象 obj,这是没有问题的。但是在嵌套函数 getX 中,this 并不指向 obj。嵌套的函数不会从调用它的函数中继承 this,当嵌套函数作为函数调用时,其 this 值在非严格模式下指向全局对象,在严格模式是 undefined。

# 作为构造函数调用

我们可以使用 new 关键字,通过构造函数生成一个实例对象。此时,this 便指向这个新对象。

var x = 1
function Demo() {
  this.x = 2
}

var a = new Demo()
console.log(a.x)    // 2
1
2
3
4
5
6
7

值得一提的是,如果构造函数返回了一个对象,this 便会指向返回的对象,如果构造函数返回了非引用类型(string,number,boolean,null,undefined),this 仍然指向实例化的新对象。

var x = 1
function Demo() {
  this.x = 2
  return {
    x: 3
  }
}

var a = new Demo()
console.log(a.x)      // 3
1
2
3
4
5
6
7
8
9
10
var x = 1
function Demo() {
  this.x = 2
  return 3
}

var a = new Demo()
console.log(a.x)      // 2
1
2
3
4
5
6
7
8

# 使用 call 和 apply

如果你想改变 this 的指向,可以使用 call 或 apply 方法,它们都可以改变函数的调用对象。将一个对象作为第一个参数传给 call 或 apply,this 便会绑定到这个对象。如果第一个参数不传或者传 null 、undefined,默认 this 指向全局对象(非严格模式)或 undefined(严格模式)。

var x =1;
var obj = {
  x: 2
}
function getX() {
  console.log(this.x)
}

getX.call(obj)      // 2
getX.apply(obj)     // 2

getX.call()         // 1
getX.apply(null)    // 1
getX.call(undefined)    // 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14

使用 call 和 apply 时,如果给 this 传的不是对象,JavaScript 会使用相关构造函数将其转化为对象,比如传 number 类型,会进行 new Number() 操作,传 string 类型,会进行 new String() 操作。

functiondemo() {
  console.log(Object.prototype.toString.call(this))
}

demo.call('hello')      // [object String]
demo.apply(5)           // [object Number]
1
2
3
4
5
6

call 和 apply 的区别在于,call 的第二个及后续参数是一个参数列表,apply 的第二个参数是数组。参数列表和参数数组都将作为函数的参数进行执行。

var x =1
var obj = {
  x: 2
}

function getSum(y, z) {
  console.log(this.x + y + z)
}

getSum.call(obj, 3, 4)       // 9
getSum.apply(obj, [3, 4])    // 9
1
2
3
4
5
6
7
8
9
10
11

# bind 方法

bind 方法会创建一个新函数,新函数的 this 会永久的指向 bind 传入的第一个参数。我们来看下面的列子。

var x =1
var obj_1 = {
  x: 2
}
var obj_2 = {
  x: 3
}

function getX() {
  console.log(this.x)
}

var a = getX.bind(obj_1)
var b = a.bind(obj_2)

getX()           // 1
a()              // 2
b()              // 2
a.call(obj_2)    // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在上面的例子中,虽然我们尝试给函数 a 重新指定 this 的指向,但是它依旧指向第一次 bind 传入的对象,即使是使用 call 或 apply 方法也不能改变这一事实。

# 箭头函数

ES6 新增了箭头函数,箭头函数不仅更加整洁,还对 this 的指向进行了改进。箭头函数会从作用域链的上一层继承 this。

在前面函数嵌套函数的例子中,被嵌套的函数不会继承上层函数的 this,如果使用箭头函数,会发生什么变化呢?

var obj= {
  y: function() {
    console.log(this === obj)
    var getX = () => {
      console.log(this === obj)
    }
    getX()
  }
}

obj.y() 
// true
// true
1
2
3
4
5
6
7
8
9
10
11
12
13

和普通函数不一样,箭头函数中的 this 指向了 obj,这是因为它从上一层的函数中继承了 this,你可以理解为箭头函数修正了 this 的指向。我们再来看个例子。

var x =1
var obj = {
  x: 2,
  y: function() {
    var getX = () => {
      console.log(this.x)
    }
    return getX()
  }
}

obj.y()            // 2
var a = obj.y
a()               // 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14

如果理解了前文,这里也是很容易理解的。obj.y() 在运行时,调用它的对象是 obj,所以 y 中的 this 指向 obj,y 中的箭头函数 getX 继承了 y 中的 this,所以结果是 2。如果我们先将 y 赋值给全局作用域中的变量 a,a 在运行时,y 中的 this 便指向了全局对象,所以得到的结果是 1(非严格模式)。

同 bind 一样,箭头函数也很“顽固”,我们无法通过 call 和 apply 来改变 this 的指向。

var x = 1
var obj = {
  x: 2
}

var a = () => {
  console.log(this.x)
}

a.call(obj)       // 1
a.apply(obj)      // 1
1
2
3
4
5
6
7
8
9
10
11

# 小结

本篇文章介绍了 this 指向的几种情况,不同的运行环境和调用方式都会对 this 产生影响。this 是 JavaScript 中非常重要的关键字,理解它能让我们更熟练地使用这门语言,也能避免踩坑。总的来说,函数 this 的指向取决于当前调用该函数的对象,也就是执行时的对象。在这一节中,你需要掌握:

  • this 指向全局对象的情况;

  • 严格模式和非严格模式下 this 的区别;

  • 函数作为对象的方法调用时 this 指向的几种情况;

  • 作为构造函数时 this 的指向,以及是否 return 的区别;

  • 使用 call 和 apply 改变调用函数的对象;

  • bind 创建的函数中 this 的指向;

  • 箭头函数中的 this 指向。