理解 JavaScript 中的 This、Bind、Call 和 Apply
作者选择开放互联网/言论自由基金作为Write for DOnations计划的一部分接受捐赠。
关键字this是 JavaScript 中一个非常重要的概念,但对于新手和有其他编程语言经验的人来说,它却是一个特别容易混淆的概念。在 JavaScript 中,this是对一个对象的引用。 所引用的对象this可能会根据其是全局变量、对象本身还是构造函数而隐式变化,也可能会根据原型Function方法bind、call和 的使用而显式变化apply。
虽然this这是一个有点复杂的话题,但它也是你开始编写第一个 JavaScript 程序时就会遇到的问题。无论你是尝试访问文档对象模型 (DOM)中的元素或事件,构建面向对象编程风格的类,还是使用常规对象的属性和方法,你都会遇到this。
在本文中,您将了解this根据上下文隐式地引用什么,以及如何使用bind、call和apply方法显式地确定的值this。
隐式上下文
有四种主要上下文this可以隐式推断出的值:
- 全球背景
- 作为对象内的方法
- 作为函数或类的构造函数
- 作为 DOM 事件处理程序
全球的
在全局上下文中,this指的是全局对象。在浏览器中工作时,全局上下文是window。在 Node.js 中工作时,全局上下文是global。
注意:如果您还不熟悉 JavaScript 中的作用域概念,请查看理解 JavaScript 中的变量、作用域和提升。
在本例中,您将在浏览器的开发者工具控制台中练习代码。如果您不熟悉如何在浏览器中运行 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.symbol内details指的是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()
this方法中的describe指的是 的实例Country,即america。
Output
"The United States of America was founded in 1776."
DOM 事件处理程序
在浏览器中,事件处理程序有一个特殊的this上下文。在由 调用的事件处理程序中addEventListener,this将引用event.currentTarget。通常情况下,开发人员会根据需要简单地使用event.target或event.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>
将其粘贴到浏览器中后,您将看到页面上添加了一个按钮,上面写着“Click me”。如果您点击该按钮,您将<button>Click me</button>在控制台中看到,因为点击该按钮会记录元素,也就是按钮本身。因此,如您所见,它this指向目标元素,也就是我们添加了事件监听器的元素。
显式上下文
在前面的所有示例中, 的值this均由其上下文决定——无论是全局变量、对象、构造函数或类,还是 DOM 事件处理程序。但是,使用call、apply或bind,您可以明确指定this应该引用的内容。
很难准确定义何时使用call、apply或bind,因为这取决于程序的上下文。bind当您想使用事件访问一个类中另一个类的属性时,这些方法尤其有用。例如,如果您要编写一个简单的游戏,您可能会将用户界面和 I/O 分离到一个类中,将游戏逻辑和状态分离到另一个类中。由于游戏逻辑需要访问输入(例如按键和点击),因此您需要使用bind事件来访问this游戏逻辑类的值。
重要的是知道如何确定对象this所指的内容,您可以使用前面章节中学到的知识隐式地完成此操作,也可以使用接下来将学习的三种方法显式地完成此操作。
致电并申请
call和apply非常相似——它们都使用指定的上下文和可选参数来调用函数。和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()
Output
"undefined was written by undefined"
由于summary和book没有联系,summary单独调用只会打印undefined,因为它正在寻找全局对象上的那些属性。
注意:在严格模式下尝试此操作将导致Uncaught TypeError: Cannot read property 'title' of undefined,因为this它本身就是undefined。
但是,您可以使用call和apply来调用函数this的上下文。book
summary.call(book)
// or:
summary.apply(book)
Output
"Brave New World was written by Aldous Huxley."
现在,这些方法的应用之间存在联系。让我们确认一下具体book是什么。summarythis
function printThis() {
console.log(this)
}
printThis.call(book)
// or:
whatIsThis.apply(book)
Output
{title: "Brave New World", author: "Aldous Huxley"}
在这种情况下,this实际上成为作为参数传递的对象。
这与call和apply相同,但有一个小区别。除了能够将上下文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,因为即使某些参数细节发生变化,也无需更改函数调用。
绑定
和call都是apply一次性使用的方法——如果你使用上下文调用该方法,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 等框架中的类方法。
结论
在本文中,您了解了thisJavaScript 中的 ,以及基于隐式运行时绑定和通过bind、call和 的显式绑定,它可能具有的众多不同值。apply您还了解了如何利用箭头函数中缺少的this绑定来引用不同的上下文。掌握这些知识后,您应该能够确定this程序中 的值。
本作品采用Creative Commons Attribution-NonCommercial-ShareAlike 4.0 国际许可协议进行许可
文章来源:https://dev.to/digitalocean/understanding-this-bind-call-and-apply-in-javascript-dla
后端开发教程 - Java、Spring Boot 实战 - msg200.com
