理解 JavaScript 中的 This、Bind、Call 和 Apply

2025-05-24

理解 JavaScript 中的 This、Bind、Call 和 Apply

作者选择开放互联网/言论自由基金作为Write for DOnations计划的一部分接受捐赠

关键字this是 JavaScript 中一个非常重要的概念,但对于新手和有其他编程语言经验的人来说,它却是一个特别容易混淆的概念。在 JavaScript 中,this是对一个对象的引用。 所引用的对象this可能会根据其是全局变量、对象本身还是构造函数而隐式变化,也可能会根据原型Function方法bindcall和 的使用而显式变化apply

虽然this这是一个有点复杂的话题,但它也是你开始编写第一个 JavaScript 程序时就会遇到的问题。无论你是尝试访问文档对象模型 (DOM)中的元素或事件,构建面向对象编程风格的类,还是使用常规对象的属性和方法,你都会遇到this

在本文中,您将了解this根据上下文隐式地引用什么,以及如何使用bindcallapply方法显式地确定的值this

隐式上下文

有四种主要上下文this可以隐式推断出的值:

  • 全球背景
  • 作为对象内的方法
  • 作为函数或类的构造函数
  • 作为 DOM 事件处理程序

全球的

在全局上下文中,this指的是全局对象。在浏览器中工作时,全局上下文是window。在 Node.js 中工作时,全局上下文是global

注意:如果您还不熟悉 JavaScript 中的作用域概念,请查看理解 JavaScript 中的变量、作用域和提升

在本例中,您将在浏览器的开发者工具控制台中练习代码。如果您不熟悉如何在浏览器中运行 JavaScript 代码,请阅读如何使用 JavaScript 开发者控制台。

this如果您记录不带任何其他代码的值,您将看到对象this所引用的内容。

console.log(this)
Enter fullscreen mode Exit fullscreen mode
Output
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Enter fullscreen mode Exit fullscreen mode

您可以看到,thiswindow是浏览器的全局对象。

理解 JavaScript 中的变量、作用域和变量提升中,你了解到函数拥有其自身的变量上下文。你可能会认为this函数内部也遵循相同的规则,但事实并非如此。顶级函数仍然会保留this全局对象的引用。

您编写一个顶级函数,或者一个不与任何对象关联的函数,如下所示:

function printThis() {
  console.log(this)
}

printThis()
Enter fullscreen mode Exit fullscreen mode
Output
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Enter fullscreen mode Exit fullscreen mode

即使在函数内部,this仍然引用window或全局对象。

然而,当使用严格模式this时,全局上下文中的函数内的上下文将是undefined

'use strict'

function printThis() {
  console.log(this)
}

printThis()
Enter fullscreen mode Exit fullscreen mode
Output
undefined
Enter fullscreen mode Exit fullscreen mode

一般来说,使用严格模式更安全,可以降低出现意外作用域的可能性this。很少有人会想用window来引用对象this

有关严格模式的更多信息以及它在错误和安全性方面所做的更改,请阅读MDN 上的严格模式文档。

对象方法

方法是对象上的函数,或者对象可以执行的任务。方法用于this引用对象的属性。

const america = {
  name: 'The United States of America',
  yearFounded: 1776,

  describe() {
    console.log(`${this.name} was founded in ${this.yearFounded}.`)
  },
}

america.describe()
Enter fullscreen mode Exit fullscreen mode
Output
"The United States of America was founded in 1776."
Enter fullscreen mode Exit fullscreen mode

在此示例中,this与 相同america

在嵌套对象中,this指的是方法的当前对象范围。在下面的示例中,对象this.symboldetails指的是details.symbol

const america = {
  name: 'The United States of America',
  yearFounded: 1776,
  details: {
    symbol: 'eagle',
    currency: 'USD',
    printDetails() {
      console.log(`The symbol is the ${this.symbol} and the currency is ${this.currency}.`)
    },
  },
}

america.details.printDetails()
Enter fullscreen mode Exit fullscreen mode
Output
"The symbol is the eagle and the currency is USD."
Enter fullscreen mode Exit fullscreen mode

另一种思考方式是,this调用方法时指的是点左侧的对象。

函数构造函数

使用new关键字时,它会创建一个构造函数或类的实例。class在 ECMAScript 2015 JavaScript 更新中引入该语法之前,函数构造函数是初始化用户定义对象的标准方法。在理解 JavaScript 中的类中,您将学习如何创建函数构造函数以及等效的类构造函数。

function Country(name, yearFounded) {
  this.name = name
  this.yearFounded = yearFounded

  this.describe = function() {
    console.log(`${this.name} was founded in ${this.yearFounded}.`)
  }
}

const america = new Country('The United States of America', 1776)

america.describe()
Enter fullscreen mode Exit fullscreen mode
Output
"The United States of America was founded in 1776."
Enter fullscreen mode Exit fullscreen mode

在此上下文中,this现在绑定到的实例Country,该实例包含在america常量中。

类构造函数

类的构造函数与函数的构造函数作用相同。请参阅《理解 JavaScript 中的类》一文,了解更多关于函数构造函数与 ES6 类之间的异同

class Country {
  constructor(name, yearFounded) {
    this.name = name
    this.yearFounded = yearFounded
  }

  describe() {
    console.log(`${this.name} was founded in ${this.yearFounded}.`)
  }
}

const america = new Country('The United States of America', 1776)

america.describe()
Enter fullscreen mode Exit fullscreen mode

this方法中的describe指的是 的实例Country,即america

Output
"The United States of America was founded in 1776."
Enter fullscreen mode Exit fullscreen mode

DOM 事件处理程序

在浏览器中,事件处理程序有一个特殊的this上下文。在由 调用的事件处理程序中addEventListenerthis将引用event.currentTarget。通常情况下,开发人员会根据需要简单地使用event.targetevent.currentTarget来访问 DOM 中的元素,但由于this引用在此上下文中会发生变化,因此了解这一点很重要。

在下面的示例中,我们将创建一个按钮,向其中添加文本,并将其附加到DOM。当我们在事件处理程序中记录 的值时this,它将打印目标。

const button = document.createElement('button')
button.textContent = 'Click me'
document.body.append(button)

button.addEventListener('click', function(event) {
  console.log(this)
})
Enter fullscreen mode Exit fullscreen mode
Output
<button>Click me</button>
Enter fullscreen mode Exit fullscreen mode

将其粘贴到浏览器中后,您将看到页面上添加了一个按钮,上面写着“Click me”。如果您点击该按钮,您将<button>Click me</button>在控制台中看到,因为点击该按钮会记录元素,也就是按钮本身。因此,如您所见,它this指向目标元素,也就是我们添加了事件监听器的元素。

显式上下文

在前面的所有示例中, 的值this均由其上下文决定——无论是全局变量、对象、构造函数或类,还是 DOM 事件处理程序。但是,使用callapplybind,您可以明确指定this应该引用的内容。

很难准确定义何时使用callapplybind,因为这取决于程序的上下文。bind当您想使用事件访问一个类中另一个类的属性时,这些方法尤其有用。例如,如果您要编写一个简单的游戏,您可能会将用户界面和 I/O 分离到一个类中,将游戏逻辑和状态分离到另一个类中。由于游戏逻辑需要访问输入(例如按键和点击),因此您需要使用bind事件来访问this游戏逻辑类的值。

重要的是知道如何确定对象this所指的内容,您可以使用前面章节中学到的知识隐式地完成此操作,也可以使用接下来将学习的三种方法显式地完成此操作。

致电并申请

callapply非常相似——它们都使用指定的上下文和可选参数来调用函数。this之间的唯一区别在于要求参数逐个传入,而则以数组形式接收参数。callapplycallapply

this在这个例子中,我们将创建一个对象,并创建一个引用但没有上下文的函数this

const book = {
  title: 'Brave New World',
  author: 'Aldous Huxley',
}

function summary() {
  console.log(`${this.title} was written by ${this.author}.`)
}

summary()
Enter fullscreen mode Exit fullscreen mode
Output
"undefined was written by undefined"
Enter fullscreen mode Exit fullscreen mode

由于summarybook没有联系,summary单独调用只会打印undefined,因为它正在寻找全局对象上的那些属性。

注意:在严格模式下尝试此操作将导致Uncaught TypeError: Cannot read property 'title' of undefined,因为this它本身就是undefined

但是,您可以使用callapply来调用函数this的上下文。book

summary.call(book)
// or:
summary.apply(book)
Enter fullscreen mode Exit fullscreen mode
Output
"Brave New World was written by Aldous Huxley."
Enter fullscreen mode Exit fullscreen mode

现在,这些方法的应用之间存在联系。让我们确认一下具体book什么summarythis

function printThis() {
  console.log(this)
}

printThis.call(book)
// or:
whatIsThis.apply(book)
Enter fullscreen mode Exit fullscreen mode
Output
{title: "Brave New World", author: "Aldous Huxley"}
Enter fullscreen mode Exit fullscreen mode

在这种情况下,this实际上成为作为参数传递的对象。

这与callapply相同,但有一个小区别。除了能够将上下文this作为第一个参数传递之外,还可以传递其他参数。

function longerSummary(genre, year) {
  console.log(
    `${this.title} was written by ${this.author}. It is a ${genre} novel written in ${year}.`
  )
}
Enter fullscreen mode Exit fullscreen mode

call您想要传递的每个附加值都会作为附加参数发送

longerSummary.call(book, 'dystopian', 1932)
Enter fullscreen mode Exit fullscreen mode
Output
"Brave New World was written by Aldous Huxley. It is a dystopian novel written in 1932."
Enter fullscreen mode Exit fullscreen mode

如果您尝试使用 发送完全相同的参数apply,则会发生以下情况:

longerSummary.apply(book, 'dystopian', 1932)
Enter fullscreen mode Exit fullscreen mode
Output
Uncaught TypeError: CreateListFromArrayLike called on non-object at <anonymous>:1:15
Enter fullscreen mode Exit fullscreen mode

相反,对于apply,您必须传递数组中的所有参数。

longerSummary.apply(book, ['dystopian', 1932])
Enter fullscreen mode Exit fullscreen mode
Output
"Brave New World was written by Aldous Huxley. It is a dystopian novel written in 1932."
Enter fullscreen mode Exit fullscreen mode

单独传递参数和通过数组传递参数之间的区别很微妙,但务必注意。使用 可能更简单、更方便apply,因为即使某些参数细节发生变化,也无需更改函数调用。

绑定

call都是apply一次性使用的方法——如果你使用上下文调用该方法,this它将拥有它,但原始功能将保持不变。

this有时,您可能需要在另一个对象的上下文中反复使用某个方法,在这种情况下,您可以使用该bind方法创建一个具有明确绑定的全新函数this

const braveNewWorldSummary = summary.bind(book)

braveNewWorldSummary()
Enter fullscreen mode Exit fullscreen mode
Output
"Brave New World was written by Aldous Huxley"
Enter fullscreen mode Exit fullscreen mode

在此示例中,每次调用braveNewWorldSummary,它都会返回this绑定到它的原始值。尝试将新的上下文绑定this到它将会失败,因此您始终可以信任绑定函数会返回this您期望的值。

const braveNewWorldSummary = summary.bind(book)

braveNewWorldSummary() // Brave New World was written by Aldous Huxley.

const book2 = {
  title: '1984',
  author: 'George Orwell',
}

braveNewWorldSummary.bind(book2)

braveNewWorldSummary() // Brave New World was written by Aldous Huxley.
Enter fullscreen mode Exit fullscreen mode

braveNewWorldSummary尽管此示例尝试再次绑定,但它保留了this第一次绑定时的原始上下文。

箭头函数

箭头函数没有自己的this绑定。相反,它们会上升到下一级执行。

const whoAmI = {
  name: 'Leslie Knope',
  regularFunction: function() {
    console.log(this.name)
  },
  arrowFunction: () => {
    console.log(this.name)
  },
}

whoAmI.regularFunction() // "Leslie Knope"
whoAmI.arrowFunction() // undefined
Enter fullscreen mode Exit fullscreen mode

当你确实需要引用外部上下文时,使用箭头函数会很有用this。例如,如果你在类内部有一个事件监听器,你可能想要this引用类中的某个值。

在这个例子中,您将像以前一样创建按钮并将其附加到 DOM,但是该类将有一个事件监听器,当单击按钮时,该事件监听器将更改按钮的文本值。

const button = document.createElement('button')
button.textContent = 'Click me'
document.body.append(button)

class Display {
  constructor() {
    this.buttonText = 'New text'

    button.addEventListener('click', event => {
      event.target.textContent = this.buttonText
    })
  }
}

new Display()
Enter fullscreen mode Exit fullscreen mode

如果点击按钮,文本内容将更改为 的值buttonText。如果此处未使用箭头函数,this则 等于event.currentTarget,并且您将无法在未显式绑定的情况下使用它来访问类中的值。这种策略通常用于 React 等框架中的类方法。

结论

在本文中,您了解了thisJavaScript 中的 ,以及基于隐式运行时绑定和通过bindcall和 的显式绑定,它可能具有的众多不同值。apply您还了解了如何利用箭头函数中缺少的this绑定来引用不同的上下文。掌握这些知识后,您应该能够确定this程序中 的值。


CC 4.0许可证

本作品采用Creative Commons Attribution-NonCommercial-ShareAlike 4.0 国际许可协议进行许可

文章来源:https://dev.to/digitalocean/understanding-this-bind-call-and-apply-in-javascript-dla
PREV
为什么你应该自行托管一切
NEXT
如何使用 Git:参考指南