乐趣区

理解-JS-中的-This-Bind-Call-和-Apply

原文在 https://www.digitalocean.com/community/conceptual_articles/understanding-this-bind-call-and-apply-in-javascript
作者是 Tania Rascia


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

this 关键字是在 JavaScript 中一个非常重要的概念,也是一个特别令人迷惑的这两个新的开发者和那些谁拥有在其他编程语言的经验。在 JavaScript 中,this是对对象的引用。该对象 this 是指可以改变,含蓄地基于它是否是全球性的,在对象上,或者在一个构造函数,也可以明确地变化根据的使用 Function 原型方法 bindcallapply

尽管这 this 是一个复杂的话题,但它也是在您开始编写第一个 JavaScript 程序后立即出现的话题。无论您是尝试访问文档对象模型(DOM)中的元素还是事件,构建用于以面向对象的编程风格编写的类,还是使用常规对象的属性和方法,都将遇到this

在本文中,您将了解什么 this 是指隐含根据上下文,您将学习如何使用 bindcallapply方法,明确确定的值this

内隐语境

在以下四个主要上下文中,this可以隐式推断出的值:

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

全局

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

注意:如果您还不熟悉 JavaScript 中范围的概念,请参阅了解 JavaScript 中的变量,范围和提升。

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

如果您在 this 没有任何其他代码的情况下记录了值,那么您将看到对象 this 所指。

console.log(this)
// Output
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

你可以看到,this就是window,这是一个浏览器的全局对象。

在“了解 JavaScript 中的变量,作用域和提升”中”中,您了解到函数具有自己的变量上下文。您可能会倾向于认为它 this 会遵循函数内的相同规则,但事实并非如此。顶层函数仍将保留 this 全局对象的引用。

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

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

printThis()
// Output
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

即使在函数内,this仍指 window 或全局对象。

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

'use strict'

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

printThis()
// ~~~~Output
undefined

通常,使用严格模式来降低 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()
// Output
"The United States of America was founded in 1776."

在此示例中,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()
Output
"The symbol is the eagle and the currency is USD."

另一种思考方式是 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()
Output
"The United States of America was founded in 1776."

在这种情况下,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()

thisdescribe 方法指的实例Country,这是america

Output
"The United States of America was founded in 1776."

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)
})
Output
<button>Click me</button>

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

显式上下文

在前面的所有示例中,的值 this 都是由其上下文确定的 - 无论它是全局的,在对象中,在构造的函数或类中还是在 DOM 事件处理程序上。但是,使用 callapplybind可以显式确定 this 应引用的内容。

很难准确定义何时使用 callapplybind,因为这将取决于程序的上下文。bind当您想使用事件来访问另一个类中一个类的属性时,它可能特别有用。例如,如果要编写一个简单的游戏,则可以将用户界面和 I / O 分成一个类,将游戏逻辑和状态分成另一个类。由于游戏逻辑需要访问输入,例如按键和单击,因此您希望 bind 事件能够访问 this 游戏逻辑类的值。

重要的部分是要知道如何确定 this 引用的对象,您可以使用上一部分中的内容隐式地进行操作,或者使用接下来将要学习的三种方法来显式地进行操作。

call apply

callapply 它们非常相似 - 它们调用具有指定 this 上下文和可选参数的函数。call和之间的唯一区别 apply 是,call要求将参数逐一传递,并将 apply 参数作为数组。

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

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

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

summary()
Output
"undefined was written by undefined"

由于 summary 并且 book 没有连接,因此在全局对象上寻找那些属性时,summary仅进行打印本身就可以调用undefined

注意:在严格模式下尝试这样做会导致 Uncaught TypeError: Cannot read property 'title' of undefined,就像this 它本身一样undefined

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

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

现在 booksummary 在应用这些方法之间以及何时应用这些方法之间存在联系。让我们确切地确认是什么this

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

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

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

这就是为何 callapply是相同的,但有一个小的差异。除了能够将 this 上下文作为第一个参数传递之外,您还可以传递其他参数。

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

随着 call 每增加值要传递被作为一个额外的参数。

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

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

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

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

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

单独或以数组形式传递参数之间的区别是微妙的,但请务必注意。它可能更简单,更方便使用apply,因为如果某些参数详细信息发生更改,则不需要更改函数调用。

bind

这两个 callapply是一次性使用的方法,如果调用该方法 this 上下文中,有它,但原有的功能将保持不变。

有时,您可能需要一遍 this 又一遍地在另一个对象的上下文中使用一种方法,在这种情况下,您可以使用该 bind 方法来创建一个带有显式绑定的全新函数this

const braveNewWorldSummary = summary.bind(book)

braveNewWorldSummary()
Output
"Brave New World was written by Aldous Huxley"

在此示例中,每次调用时 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.

尽管此示例尝试 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

在确实要 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()

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

结论

在本文中,您了解了 this 在 JavaScript,和许多不同的值,可能已经基于隐式运行时绑定,并明确通过结合 bindcallapply。您还了解了如何 this 使用箭头功能中缺少绑定的方式来引用不同的上下文。有了这些知识,您应该能够确定程序中的价值this


搬运一篇文章,机翻。
没有公众号需要你关注。

退出移动版