
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!