JavaScript: 理解this是很容易
引言
本文将介绍JavaScript的另一个基本但同样被误解的方面,称为this
关键字。
好吧,让我们开始吧。在本文的结尾,您应该对以下内容有更好的了解,
在JavaScript中this
是什么。
- 如何使
this
听起来比以往更少混乱 this
的规则及其用法。- 重要的是,
this
很容易!
冗长的文章阅读提醒
与本系列的其他文章不同,该文章将有些冗长。看完几本教程后,我觉得应该将各个方面联系起来,以很好地理解this
一概念。例如,call
=> apply
=> bind
的概念与理解this
关键字非常相关。我们需要一起讨论。
我可以将其分解为多篇文章,但是最好将它们放在一起,因为这些概念是相互关联的。
带上您最喜欢的饮料,放松身心,然后开始阅读。我相信您一定会喜欢的。
this
是什么?
this
是JavaScript中的关键字,它的存在是为了允许我们,
- 在不同的上下文中重用函数。
- 标识在调用方法时要关注的对象。
请记住,在JavaScript中创建执行上下文时,它会创建一个称为this
的特殊实体。
在全局执行上下文中,这等于全局对象window
。
在函数执行上下文中,默认情况下,this
等于window
对象,但是行为根据称为绑定的现象而改变。不要将其与JavaScript中的bind()
方法混淆。 bind()
方法只是绑定内容的另一种方法。还有更多。我们将在一段时间内看到它,并且this
90%的用法都围绕它。
this
的规则
理解函数的this
关键字可能具有挑战性,因为它在JavaScript中的行为不同于其他语言。当涉及到这一点时,要问的重要问题是,
该函数在哪里调用?在调用函数之前,我们不知道
this
关键字中包含什么。
this
的用法可以分为四个不同的绑定方面。
隐私绑定(Implicit binding)
隐式绑定涵盖了我们使用this
关键字处理的大多数用例。在隐式绑定中,在调用时与函数相邻的dot(.)
运算符的左侧确定this
对象绑定到什么。
这里有一个例子,
1 | var user = { |
说明:在上面的示例中,this
绑定到user
对象,与函数getName()
相邻的dot(.)
运算符的左侧是user
对象。因此,this.name
将在控制台中记录GreenRoots
。
让我们再举一个例子来更好地解释这个概念,
例如:
1 | function decorateLogName(obj) { |
说明:在上面的示例中,我们有两个对象,tom
和jerry
。我们通过附加一个名为logName()
的方法来修饰(增强)这些对象。
请注意,当我们调用tom.logName()
时,与函数logName()
相邻的dot(.)
运算符的左侧是tom
对象。因此,this
绑定到tom
对象,并记录值tom
(this.name
等于tom
)。调用jerry.logName()
时,同样适用。
够清楚了吗?好!现在,让我们以JavaScript类为例,它只是一个函数,但是我们可以创建它的不同实例,
例如:
1 | var Cartoon = function (name, age, friend) { |
说明:在这里,我们有一个名为Cartoon
的类。卡通有一个名为logName()
的方法。它也有一个叫做friend
的属性,它是一个对象。为了使事情有些棘手,对象friend
也有一个名为logName()
的方法。
实例化Cartoon
类后,我们得到一个名为tom
的实例,该实例传递名称,即Tom
,age
为7,而friend
的名为Jerry
。
调用tom.logName()
时,与函数logName()
相邻的dot(.)
运算符的左侧是tom
,并且this
绑定tom
。因此,这里的this.name
指向tom
的名字,我们之前以tom
的名字传递它。
调用tom.friend.logName()
时,与函数logName()
相邻的dot(.)
运算符的左侧为friend
。因此,this
指向对象friend
,this.name
是对象friend
的名字,即Jerry
。
我希望我们充分理解隐式绑定的规则和逻辑以应用和实现它。让我们继续前进。
显式绑定(Explicit binding)
我们知道,JavaScript创建了一个环境来执行我们编写的代码。该环境包含的内容超出了我们编写的实际代码。
在创建阶段,它负责为变量,函数,对象等创建内存。最后,在执行阶段执行代码。这种特殊的环境称为JavaScript执行上下文。您可以从这里阅读更多有关它的信息。
JavaScript应用程序中有许多这样的环境(执行上下文)。每个执行上下文彼此独立地操作。但是有时,我们可能希望使用从一个执行上下文到另一个执行上下文的东西。这就是显式绑定起作用的地方。我们可以将内容从一个上下文绑定到另一环境的上下文中,以使用this
上下文执行。
在显式绑定中,我们可以在对象的执行上下文之外调用带有对象的函数。
共有三种非常规方法,call()
,apply()
和bind()
有助于实现显式绑定。
call()
方法
使用call()
方法,必须将调用函数的上下文作为参数传递给call()。让我们看一个例子,
例如:
1 | var getName = function() { |
说明:我们在这里看到的是,getName()
的函数上调用了call()
方法。 getName()
函数仅仅记录this.name
。但是,这里的this
是什么?这取决于传递给call()
方法的内容。
在这里,this
将绑定到user
对象,因为我们已经将user
作为参数传递给call()
方法。因此,this.name
应该记录user
对象的name
属性的值,即GreenRoots
。
在上面的示例中,我们仅将一个参数传递给call()
。但是,如果需要,我们可以将多个参数传递给call()
。让我们再举一个例子来了解这一点,
例如
1 | var getName = function(hobby1, hobby2) { |
说明:注意,我们在这里在call()
方法中传递了另外两个参数。第一个参数必须是必须使用该函数调用的对象上下文。其他参数可能只是要使用的值。在这里,我将Swimming
和Blogging
作为两个参数传递给getName()
函数。 您在这里注意到痛点了吗?在使用call()
的情况下,参数必须一个接一个地传递,这不是一种明智的处理方式!这就是我们的下一个方法apply()
讲解的。
apply()
方法
可以通过另一种名为apply()
的替代方法来解决将参数传递给call()
方法的麻烦方法。它与call()
相同,但允许更方便地传递参数。看一看,
例如
1 | var getName = function(hobby1, hobby2) { |
说明:如您在此处看到的,我们可以将数组作为参数传递,这比逐个传递要方便得多。
当您只传递一个值参数或没有值参数时,请使用
call()
。当您有多个要传递的值参数时,请使用apply()
。
bind()
方法
call()
方法通过传递this
上下文来调用该函数。 bind()
方法与call()
类似,但是bind()
不会直接调用该函数,而是返回一个全新的函数,我们可以调用它。
例如
1 | var getName = function(hobby1, hobby2) { |
说明:如上所述,getName.bind()
不会调用函数getName()
。它返回一个新函数newFn
,我们可以像newFn()
那样调用它。
new
绑定
使用new
关键字创建一个构造函数。这是一个构造函数的例子,
1 | var Cartoon = function(name, animal) { |
我们可以使用new
关键字创建对象,
1 | var tom = new Cartoon('Tom', 'Cat'); |
构造函数的
new
绑定规则规定,当使用new
关键字调用函数时,函数内的this
关键字绑定到正在构造的新对象。
听起来复杂吗?好吧,让我们分解一下。走这条线,
1 | var tom = new Cartoon('Tom', 'Cat'); |
在这里,函数Cartoon
是通过new
关键字调用的。因此,this
将绑定到此处创建的新对象tom
。
全局对象绑定
该代码执行的输出是什么?this
绑定到这里是什么?
1 | var sayName = function(name) { |
如果this
关键字未通过上述任何绑定(隐式
,显式
或new
绑定)解析,则this
绑定到window
(global
)对象。
箭头函数,没有绑定吗?
ES6引入了箭头函数,这些功能不提供自己的this
绑定。到目前为止,我们已经看到,在常规函数中,this
关键字表示调用该函数的对象,该对象可以是window
,文档,用户定义的对象或任何其他内容。
使用箭头函数时,
this
关键字始终代表定义了箭头函数的对象。
箭头函数使用词法作用域,“ this
”是指其当前的作用域。箭头函数不绑定自己的作用域,而是从父级继承它。
时间示例。让我们看看它是的工作。
1 | var testHobbies = { |
在这里,logHobbies()
方法遍历这些兴趣爱好并将其记录到控制台中。注意,我们在forEach
中使用箭头函数。箭头函数内部的this
将绑定到对象testHobbies
,因为箭头函数没有此绑定,并且始终绑定到父对象。
因此,调用testHobbies.logHobbies()
将正确记录为:
1 | Alex knows Cricket |
现在让我们改变一下。请注意我在下面所做的修改。 for-each
使用常规函数代替箭头函数。
1 | var testHobbies = { |
您怎么看,this
将绑定到forEach
中?它不是箭头函数。它是一个常规函数,并且具有自己的执行上下文。在该执行上下文中,找不到一个叫name
。因此,this.name
是undefined
。
因此将输出
1 | undefined knows Cricket |
我们将在以后有关Scope
和闭包
的文章中更详细地介绍它。
使用严格模式和this
通常,在全局作用域内,this
关键字指的是window
对象,
1 | <script> |
同样在JavaScript严格模式下,全局作用域内的this
关键字返回window
对象。但是,它在函数作用域内的行为有所不同。
看下面例子
1 | <script> |
它将在控制台中记录以下输出,
1 | Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …} |
总结
是的,this
很容易理解!但是同时,理解this
规则和用法可能会很困难。当我们专注于以下问题时,我们将更好地理解this
一点:在哪里调用函数? 在大多数情况下,将与隐式绑定一起使用。显式绑定将与call()
,apply()
和bind()
一起使用。在许多基于JavaScript的框架(如Reactjs,Angular等)中,我们使用箭头函数。 只需注意,只要您理解并实践了这些规则,我相信您会同意this
确实很容易使用!
后记
翻译来自JavaScript: this is easy and what do you need to know about it!