理解 JavaScript 的“this”关键字

在JavaScript中,`this` 关键字究竟代表什么?在实际的JavaScript程序中,我们应该如何运用它? 这些都是JavaScript初学者,甚至是某些经验丰富的开发者,经常会提出的关于`this`关键字的疑问。

如果您也是对`this`关键字的含义感到困惑的开发者之一,那么这篇文章将为您解惑。本文将深入探讨`this`在不同场景下的指向,并帮助您熟悉一些容易导致混淆的陷阱,当然,还有如何在代码中避免错误。

全局作用域中的 `this`

在全局作用域下,当不在任何函数内部时,`this` 会指向全局的 `window` 对象。 全局作用域指的是你直接在脚本中编写代码,而不是在任何函数内部。

 if(true) {
console.log(this)
}

let i = 2
while(i < 10) {
console.log(this)
i++
}

如果您运行上面的代码,控制台会输出 `window` 对象。

函数(方法)中的 `this`

在函数内部使用时,`this` 通常指向调用该函数的对象。 但有一个例外:当在独立函数中使用 `this` 时,它会指向 `window` 对象。让我们看一些具体的例子。

在下面的例子中,`sayName` 函数存在于 `me` 对象内部(因此它是一个方法)。在这种情况下,`this` 指向的是包含该函数的 `me` 对象。

 
function sayName() {
return `我的名字是 ${this.name}`
}

const me = {
name: "张三",
sayName: sayName
}

console.log(me.sayName())

`this` 这里是 `me` 对象,所以在 `sayName` 方法中,`this.name` 等同于 `me.name`。

换个角度理解,调用函数时,函数左侧的内容就是 `this` 的指向。这意味着你可以在不同的对象中复用 `sayName` 函数,每次 `this` 都会指向完全不同的上下文。

正如之前提到的,当在独立函数中使用 `this` 时,它会指向 `window` 对象。这是因为默认情况下,独立函数会被绑定到 `window` 对象:

 function talk() {
return this
}

talk()

调用 `talk()` 等同于调用 `window.talk()`,函数左侧的内容会自动成为 `this` 的指向。

顺便提一下,在JavaScript的严格模式下,函数中 `this` 关键字的行为有所不同(它会返回 `undefined`)。当您使用像 React 这样使用严格模式的UI库时,也需要注意这一点。

`Function.bind()` 方法对 `this` 的运用

在某些情况下,您可能无法直接将函数作为方法添加到对象中(如上一节所示)。

也许这个对象不属于您,而是您从某个库中获取的。这个对象是不可变的,因此你不能直接修改它。在这种情况下,您仍然可以使用 `Function.bind()` 方法,将函数与对象分开执行。

在下面的例子中,`sayName` 函数并非 `me` 对象的方法,但您仍然可以使用 `bind()` 函数将其绑定到 `me` 对象上:

 function sayName() {
return `我的名字是 ${this.name}`
}

const me = {
name: "张三"
}

const meTalk = sayName.bind(me)

meTalk()

传递给 `bind()` 的任何对象都将作为该函数调用中 `this` 的值。

总而言之,您可以在任何函数上使用 `bind()` 并传入一个新的上下文(对象)。这个对象会覆盖函数内部 `this` 的含义。

`Function.call()` 方法对 `this` 的运用

如果您不想返回一个全新的函数,而是只想在将函数绑定到某个上下文后立即调用它,该怎么办? 解决方案是 `call()` 方法:

 function sayName() {
return `我的名字是 ${this.name}`
}

const me = {
name: "张三"
}

sayName.call(me)

`call()` 方法会立即执行函数,而不是返回一个新的函数。

如果函数需要参数,可以通过 `call()` 方法传递。在下面的例子中,我们将语言传递给 `sayName()` 函数,以便可以根据条件返回不同的信息:

 function sayName(lang) {
if (lang === "en") {
return `My name is ${this.name}`
} else if (lang === "it") {
return `Io sono ${this.name}`
}
}

const me = {
name: "张三"
}

sayName.call(me, 'en')
sayName.call(me, 'it')

正如您所看到的,您可以将所需的任何参数作为 `call()` 方法的第二个参数传递给函数。 您还可以传递任意数量的参数。

`apply()` 方法与 `call()` 和 `bind()` 非常相似。唯一的区别是,使用 `call()` 时,可以通过逗号分隔来传递多个参数,而使用 `apply()` 时,则在一个数组内传递多个参数。

总结来说,`bind()`、`call()` 和 `apply()` 都允许您使用完全不同的对象来调用函数,这两个对象之间没有任何关系(即函数不是对象的方法)。

构造函数中的 `this`

如果使用 `new` 关键字来调用函数,它会创建一个 `this` 对象并返回它:

 function person(name){
this.name = name
}

const me = new person("张三")
const her = new person("李四")
const him = new person("王五")

me.name
her.name
him.name

在上面的代码中,您从同一个函数创建了三个不同的对象。`new` 关键字会自动在正在创建的对象和函数内的 `this` 关键字之间创建绑定。

回调函数中的 `this`

回调函数与常规函数不同。回调函数是作为参数传递给另一个函数的函数,因此它们可以在主函数执行完毕后立即执行。

当在回调函数中使用时,`this` 关键字会指向完全不同的上下文:

 function person(name){
this.name = name
setTimeout(function() {
console.log(this)
}, 1000)
}

const me = new person("张三")

调用 `person` 构造函数并创建一个新的 `me` 对象一秒钟后,它会将 `window` 对象记录为 `this` 的值。因此,当在回调函数中使用时,`this` 指向的是 `window` 对象,而不是“构造的”对象。

有两种方法可以解决这个问题。第一种方法是使用 `bind()` 将 `person` 函数绑定到新构造的对象:

 function person(name){
this.name = name
setTimeout(function() {
console.log(this)
}.bind(this), 1000)
}

const me = new person("张三")

通过上述修改,回调中的 `this` 将指向与构造函数(`me` 对象)相同的 `this`。

解决回调函数中 `this` 问题的第二种方法是使用箭头函数。

箭头函数中的 `this`

箭头函数与常规函数不同。您可以将回调函数设为箭头函数。使用箭头函数,您不再需要 `bind()`,因为它会自动绑定到新构造的对象:

 function person(name){
this.name = name
setTimeout(() => {
console.log(this)
}, 1000)
}

const me = new person("张三")

深入了解 JavaScript

您已经了解了 `this` 关键字及其在 JavaScript 中不同上下文下的含义。如果您是 JavaScript 的新手,学习JavaScript的所有基础知识以及它的工作原理将使您受益匪浅。