在JavaScript中this
关键字是一个重要的概念,也是一个挺让人困惑的知识点。在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的变量,作用域和提升机制。
如果在没有任何其他代码的情况下记录“this”的值,您将看到“this”指的是什么对象。
console.log(this)
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
您可以看到this
是window
,它是浏览器的全局对象。
在理解JavaScript的变量,作用域和提升机制中,您了解到函数有自己的变量上下文。您可能会认为“this”在函数中遵循相同的规则,但实际上并非如此。顶级函数仍将保留全局对象的“this”引用。
例如:
function printThis() {
console.log(this)
}
printThis()
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
即使在函数中,this
仍然指的是window
或全局对象。
但是, 当使用严格模式, 全局上下文的函数中的this
将指为undefined
。
'use strict'
function printThis() {
console.log(this)
}
printThis()
undefined
一般来说,使用严格模式来降低“this”具有意外范围的可能性更为安全。很少有人想使用this来引用“window”对象。
对象方法
方法是一个对象的函数或者也可以理解成对象可以执行的任务。方法可以使用this
引用对象中的属性。
const america = {
name: 'The United States of America',
yearFounded: 1776,
describe() {
console.log(`${this.name} was founded in ${this.yearFounded}.`)
},
}
america.describe()
"The United States of America was founded in 1776."
在这个例子中,this
就代表america
。
在嵌套对象中,this
指方法当前对象的作用域。在下面的例子中 在detail
中的this.symbol
代表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()
"The symbol is the eagle and the currency is USD."
另一种思考方法是,“this”指的是调用方法时点左侧的对象。
函数构造器
当你使用 new
关键词时,它创建了一个函数或类构造器实例。在有ES5的时代,函数构造函数是初始化用户定义对象的标准方法 。
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()
"The United States of America was founded in 1776."
上面,this
现在与country
的实例绑定。
类构造器
类构造器和函数构造器的功能类似。
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
现在与country
的实例绑定。
"The United States of America was founded in 1776."
DOM事件处理程序
在浏览器中,this
在事件处理程序具有特殊的上下文。在由调用的事件处理程序中addEventListener
,this
将引用event.currentTarget
。开发人员通常会简单地根据需要使用event.target
或event.currentTarget
访问DOM
中的元素,但是由于this
引用在此上下文中发生了变化,因此了解这一点很重要。
在下面例子中,我们将创建一个button, 给它加文本, 然后加入 DOM中。当我们打印事件处理程序中this
的值,它将打印出target
值
const button = document.createElement('button')
button.textContent = 'Click me'
document.body.append(button)
button.addEventListener('click', function(event) {
console.log(this)
})
<button>Click me</button>
因为单击该按钮将记录元素,即按钮本身。因此,this
指的是目标元素,就是我们向其添加事件侦听器的元素button。
显示上下文
在所有之前的例子中,this
的值都被它的上下文决定,无论它是全局的、在对象中的、在构造的函数或类中的,还是在dom
事件处理程序中的。但是你要是使用call
,apply
,bind
,就可以显示的决定this
指向的值。
很难准确的决定何时使用call
, apply
和bind
,因为这它取决于你的程序的上下文。当你在另一个类中想用事件去访问一个类的属性,最好用bind
。例如,如果你打算写一个简单的游戏,可以把用户接口和I/O写在一个类中,把游戏逻辑和状态写在另一个类中。那游戏逻辑肯定需要譬如按键和点击的输入,这时您需要bind
事件以访问游戏逻辑类的this
值。
Call 和 Apply
call
和apply
非常相似,它们使用指定的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()
"undefined was written by undefined"
由于summary
和book
没有连接,单独调用summary
只会打印undefined
,因为它正在全局对象上查找这些属性。
但是,可以使用call
和apply
在函数上调用book
的this
上下文。
summary.call(book)
// or:
summary.apply(book)
"Brave New World was written by Aldous Huxley."
应用call
和apply
方法后,book
和summary
之间就存在连接。我们就知道this
指的是啥了。
function printThis() {
console.log(this)
}
printThis.call(book)
// or:
whatIsThis.apply(book)
{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)
"Brave New World was written by Aldous Huxley. It is a dystopian novel written in 1932."
你要是和call一样传递这些参数到apply就会出现以下问题:
longerSummary.apply(book, 'dystopian', 1932)
Uncaught TypeError: CreateListFromArrayLike called on non-object at <anonymous>:1:15
相反,对于apply
,必须传递数组中的所有参数。
longerSummary.apply(book, ['dystopian', 1932])
"Brave New World was written by Aldous Huxley. It is a dystopian novel written in 1932."
Bind
call
和apply
都是一次性使用方法,如果使用this
上下文调用该方法,它将拥有该方法,但原始函数将保持不变。
有时,可能需要在另一个对象的this
上下文中反复使用一个方法,在这种情况下,可以使用bind
方法创建一个具有显式绑定this
的全新函数。
const braveNewWorldSummary = summary.bind(book)
braveNewWorldSummary()
"Brave New World was written by Aldous Huxley"
在本例中,每次调用bravelnewworldsummmary
时,它总是返回绑定原始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.
尽管此示例再次尝试绑定bravelnewworldsummmary
,但它还是第一次绑定时就保留了原始的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等框架中的类方法。
结论
在本文中,您了解了JavaScript中的this
,以及基于隐式绑定和通过bind
、call
和apply
进行显式绑定。您还了解了如何使用箭头函数中缺少this
,通过绑定来引用不同的上下文。有了这些知识,您应该能够确定程序中this
的值。