作者选择了 开放式互联网 / 自由言论基金作为 写给捐赠计划的一部分接受捐款。
在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.target
或event.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中的这
以及它可能基于暗示运行时间绑定以及通过绑定
、呼叫
和应用
来明确绑定的许多不同的值。