理解 JavaScript 中的此、绑定、调用和应用

作者选择了 开放式互联网 / 自由言论基金作为 写给捐赠计划的一部分接受捐款。

在JavaScript中,是一个对象的引用,而所指的对象可以根据它是全球性的、对象的或构建器的含义而有所不同,并且也可以根据使用函数原型方法绑定调用应用来明确地有所不同。

虽然这是有点复杂的话题,但它也出现在你开始编写你的第一个JavaScript程序时,无论你是试图访问文档对象模型(DOM)中的某个元素或事件(https://www.digitalocean.com/community/tutorial_series/understanding-the-dom-document-object-model),构建以对象为导向的编程风格的写作类,还是使用常规对象的属性和方法,你都会遇到这个

在本文中,您将学习指的是什么,并将学习如何使用绑定调用应用方法来明确确定的值。

隐含的背景

有四个主要的背景,可以暗示的价值:

  • 全球背景
  • 作为对象中的方法
  • 作为函数或类的构造者
  • 作为 DOM 事件处理器

全球

在全球背景下,指的是 全球对象。当您在浏览器中工作时,全球背景将是窗口

<$>[注] 注: 如果您还不熟悉JavaScript中的范围概念,请查看 理解JavaScript中的变量,范围和提升

例如,如果您不熟悉在浏览器中运行JavaScript代码,请阅读 如何使用JavaScript Developer Console

如果您在没有任何其他代码的情况下登录的值,您将看到指的是什么对象。

1console.log(this)
1[secondary_label Output]
2Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

您可以看到,窗口,它是浏览器的全球对象。

理解JavaScript中的变量、范围和提升中,您了解到函数对于变量有自己的背景,您可能会被诱惑认为会在函数内部遵循相同的规则,但它不是。

您可以写一个顶级函数,或者一个与任何对象无关的函数,如下:

1function printThis() {
2  console.log(this)
3}
4
5printThis()
1[secondary_label Output]
2Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

即使在函数内,仍然指窗口或全球对象。

但是,在使用 严格模式时,在全球背景下的函数中的背景将是未定义的

1'use strict'
2
3function printThis() {
4  console.log(this)
5}
6
7printThis()
1[secondary_label Output]
2undefined

一般来说,使用严格模式更安全,以减少具有意想不到的范围的概率,很少有人使用来参考窗口对象。

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

对象方法

方法是对象的函数,或一个对象可以执行的任务。

 1const america = {
 2  name: 'The United States of America',
 3  yearFounded: 1776,
 4
 5  describe() {
 6    console.log(`${this.name} was founded in ${this.yearFounded}.`)
 7  },
 8}
 9
10america.describe()
1[secondary_label Output]
2"The United States of America was founded in 1776."

在这个例子中,美国相同。

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

 1const america = {
 2  name: 'The United States of America',
 3  yearFounded: 1776,
 4  details: {
 5    symbol: 'eagle',
 6    currency: 'USD',
 7    printDetails() {
 8      console.log(`The symbol is the ${this.symbol} and the currency is ${this.currency}.`)
 9    },
10  },
11}
12
13america.details.printDetails()
1[secondary_label Output]
2"The symbol is the eagle and the currency is USD."

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

功能建设者

当你使用 new 关键字时,它会创建一个构建函数或类的实例. 函数构建器是在 ECMAScript 2015 更新中引入语法之前,初始化用户定义对象的标准方法。

 1function Country(name, yearFounded) {
 2  this.name = name
 3  this.yearFounded = yearFounded
 4
 5  this.describe = function() {
 6    console.log(`${this.name} was founded in ${this.yearFounded}.`)
 7  }
 8}
 9
10const america = new Country('The United States of America', 1776)
11
12america.describe()
1[secondary_label Output]
2"The United States of America was founded in 1776."

在这种背景下,现在与国家的实例有关,该实例包含在美国常数中。

一流的建设者

类中的构建器与函数上的构建器相同。 有关函数构建器和 ES6 类之间的相似性和差异,请参阅 理解 JavaScript 中的类

 1class Country {
 2  constructor(name, yearFounded) {
 3    this.name = name
 4    this.yearFounded = yearFounded
 5  }
 6
 7  describe() {
 8    console.log(`${this.name} was founded in ${this.yearFounded}.`)
 9  }
10}
11
12const america = new Country('The United States of America', 1776)
13
14america.describe()

描述方法中,指的是国家的实例,即america

1[secondary_label Output]
2"The United States of America was founded in 1776."

一个 DOM 事件处理器

在浏览器中,对于事件处理器来说,有一个特殊的this环境。在一个名为addEventListener的事件处理器中,this会指event.currentTarget。 更常见的是,开发人员会根据需要简单地使用event.targetevent.currentTarget来访问 DOM 中的元素,但由于this的参考在这种情况下发生了变化,所以重要的是要知道。

在下面的示例中,我们将创建一个按钮,添加文本,并将其附加到 DOM

1const button = document.createElement('button')
2button.textContent = 'Click me'
3document.body.append(button)
4
5button.addEventListener('click', function(event) {
6  console.log(this)
7})
1[secondary_label Output]
2<button>Click me</button>

一旦插入到浏览器中,你会看到一个按钮附在页面上,上面说点击我。如果你点击按钮,你会看到<按钮>点击我</按钮>在你的控制台中出现,因为点击按钮会登录元素,这就是按钮本身。

明确的背景

在所有之前的示例中,的值是由其背景决定的,无论是全球性的,在对象中,在构建的函数或类中,还是在DOM事件处理器上。

很难准确地定义何时使用调用应用绑定,因为这取决于你的程序的背景。绑定特别有用,当你想使用事件来访问另一个类内的某一类的属性时。

重要的一部分是知道如何确定指的是什么对象,你可以用你在上一节中学到的东西暗示地做,或者用你下一步学习的三种方法明确地做。

呼叫和应用

「呼叫」和「應用」是非常相似的,他們召喚一個函數具有指定的「這」背景,以及可選的論點.「呼叫」和「應用」之間的唯一區別是「呼叫」要求論點以一個為一,而「應用」則把論點作為一個數列。

在本示例中,我们将创建一个对象,并创建一个引用但没有背景的函数。

 1const book = {
 2  title: 'Brave New World',
 3  author: 'Aldous Huxley',
 4}
 5
 6function summary() {
 7  console.log(`${this.title} was written by ${this.author}.`)
 8}
 9
10summary()
1[secondary_label Output]
2"undefined was written by undefined"

由于摘要书籍没有联系,所以自称召唤摘要只会打印未定义,因为它在全球对象上寻找这些属性。

<$>[注] ** 注:** 在严格模式下尝试这样做会导致 Uncaught TypeError: Cannot read property 'title' of undefined,因为‘这’本身将是 undefined

但是,您可以使用调用应用来调用函数上的背景。

1summary.call(book)
2// or:
3summary.apply(book)
1[secondary_label Output]
2"Brave New World was written by Aldous Huxley."

当这些方法被应用时,总结之间现在存在联系,让我们确认是什么。

1function printThis() {
2  console.log(this)
3}
4
5printThis.call(book)
6// or:
7whatIsThis.apply(book)
1[secondary_label Output]
2{title: "Brave New World", author: "Aldous Huxley"}

在这种情况下,实际上成为作为一个论点传递的对象。

这就是为什么调用应用是相同的,但有一个小区别.除了能够通过这个背景作为第一个论点,您还可以通过其他论点。

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

通过调用将您想要传输的每个额外值发送为额外的参数。

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

如果您尝试用应用发送完全相同的参数,则会发生这样的情况:

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

相反,要应用,你必须通过一个数组中的所有参数。

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

单独或在数组中传输参数之间的区别是微妙的,但要注意,使用应用可能更简单、更方便,因为如果某些参数细节发生变化,则不需要更改函数调用。

约束

调用应用都是一次性使用方法,如果你将该方法与这个背景调用,它将具有它,但原始函数将保持不变。

有时,您可能需要在另一个对象的这个背景下反复使用一个方法,在这种情况下,您可以使用绑定方法创建一个全新的函数,其中有一个明确绑定的这个

1const braveNewWorldSummary = summary.bind(book)
2
3braveNewWorldSummary()
1[secondary_label Output]
2"Brave New World was written by Aldous Huxley"

在此示例中,每次你调用braveNewWorldSummary,它将始终返回与它绑定的原始值,试图将新的背景绑定到它将失败,因此您可以始终信任一个绑定函数返回您预期的值。

 1const braveNewWorldSummary = summary.bind(book)
 2
 3braveNewWorldSummary() // Brave New World was written by Aldous Huxley.
 4
 5const book2 = {
 6  title: '1984',
 7  author: 'George Orwell',
 8}
 9
10braveNewWorldSummary.bind(book2)
11
12braveNewWorldSummary() // Brave New World was written by Aldous Huxley.

虽然这个例子试图再次绑定勇敢NewWorldSummary,但它保留了它第一次绑定时的原始这个背景。

箭头功能

箭头函数没有自己的绑定,而不是,他们上升到下一个执行级别。

 1const whoAmI = {
 2  name: 'Leslie Knope',
 3  regularFunction: function() {
 4    console.log(this.name)
 5  },
 6  arrowFunction: () => {
 7    console.log(this.name)
 8  },
 9}
10
11whoAmI.regularFunction() // "Leslie Knope"
12whoAmI.arrowFunction() // undefined

使用箭头函数在你真正想要指向外部背景的情况下可能是有用的,例如,如果你有一个事件收听器在一个类,你可能希望指向类中的某个值。

在此示例中,您将像以前一样创建并附加按钮到 DOM,但该类将有一个事件倾听器,在点击时将更改按钮的文本值。

 1const button = document.createElement('button')
 2button.textContent = 'Click me'
 3document.body.append(button)
 4
 5class Display {
 6  constructor() {
 7    this.buttonText = 'New text'
 8
 9    button.addEventListener('click', event => {
10      event.target.textContent = this.buttonText
11    })
12  }
13}
14
15new Display()

如果您点击按钮,文本内容将更改为按钮文本值. 如果您没有在这里使用箭头函数,那么将等于event.currentTarget,并且您将无法使用它来访问类内的值而不明确绑定它。

结论

在本文中,您了解了JavaScript中的以及它可能基于暗示运行时间绑定以及通过绑定呼叫应用来明确绑定的许多不同的值。

Published At
Categories with 技术
comments powered by Disqus