【第764期】你不懂JS:this是什么?

图片
前言前端


临近年底,相信接下来的文章正是你所须要的。本文由前端早读课专栏做者@HetfieldJoe翻译受权原创分享。闭包

ps:若是想看代码,可经过点击图片查看ide


正文从这开始~函数


这是 你不懂JS:this与对象原型 第一章:this是什么?学习


JavaScript中最使人困惑的机制之一就是this关键字。它是一个在每一个函数做用域中自动定义的特殊标识符关键字,但即使是一些老练的开发者也对它到底指向什么感到困扰。this


任何足够 先进 的技术都跟魔法没有区别。-- Arthur C. Clarkespa


JavaScript的this机制实际上没有 那么 先进,可是开发者们老是在大脑中引用这句话来表达“复杂”和“混乱”,毫无疑问,若是没有清晰的理解,在 你的 困惑中this可能看起来就是彻头彻尾的魔法。翻译


注意: “this”这个词是在通常的论述中极经常使用的代词。因此,特别是在口头论述中,很难肯定咱们是在将“this”做为一个代词使用,仍是在将它做为一个实际的关键字识别符使用。为了表意清晰,我会老是使用this来表明特殊的关键字,而在其余状况下使用“this”或 this 或this。设计


为何用 this?3d

若是对于那些老练的JavaScript开发者来讲this机制都是如此的使人费解,那么有人会问为何这种机制会有用?它带来的麻烦不是比好处多吗?在讲解 如何 有用以前,咱们应当先来看看 为何 有用。


让咱们试着展现一下this的动机和用途:

图片


若是这个代码段 如何 工做让你困惑,不要担忧!咱们很快就会讲解它。只是简要地将这些问题放在旁边,以便于咱们能够更清晰的探究 为何。


这个代码片断容许identify()和speak()函数对多个 环境 对象(me和you)进行复用,而不是针对每一个对象定义函数的分离版本。


与使用this相反地,你能够明确地将环境对象传递给identify()和speak()。

image.png


然而,this机制提供了更优雅的方式来隐含地“传递”一个对象引用,致使更加干净的API设计和更容易的复用。


你的使用模式越复杂,你就会越清晰地看到:将执行环境做为一个明确参数传递,一般比传递this执行环境要乱。当咱们探索对象和原型时,你将会看到一组能够自动引用恰当执行环境对象的函数是多么有用。


困惑

咱们很快就要开始讲解this是如何 实际 工做的,但咱们首先要摒弃一些误解——它实际上 不是 如何工做的。


在开发者们用太过于字面的方式考虑“this”这个名字时就会产生困惑。这一般会产生两种臆测,但都是不对的。


它本身

第一种常见的倾向是认为this指向函数本身。至少,这是一种语法上的合理推测。


为何你想要在函数内部引用它本身?最一般的理由是递归(在函数内部调用它本身)这样的情形,或者是一个在第一次被调用时会解除本身绑定的事件处理器。


初次接触JS机制的开发者们一般认为,将函数做为一个对象(JavaScript中全部的函数都是对象!),可让你在方法调用之间储存 状态(属性中的值)。这固然是可能的,并且有一些有限的用处,但这本书的其他部分将会阐述许多其余的模式,提供比函数对象 更好 的地方来存储状态。


过一下子咱们将探索一个模式,来展现this是如何不让一个函数像咱们可能假设的那样,获得它自身的引用的。


考虑下面的代码,咱们试图追踪函数(foo)被调用了多少次:

image.png


foo.count 依然 是0, 即使四个console.log语句明明告诉咱们foo(..)实际上被调用了四次。这种失败来源于对于this (在this.count++中)的含义进行了 过于字面化 的解释。


当代码执行foo.count = 0时,它确实在函数对象foo中加入了一个count属性。可是对于函数内部的this.count引用,this其实 根本就不 指向那个函数对象,即使属性名称同样,但根对象也不一样,于是产生了混淆。


注意: 一个负责任的开发者 应当 在这里提出一个问题:“若是我递增的count属性不是我觉得的那个,那是哪一个count被我递增了?”。实际上,若是他再挖的深一些,他会发现本身不当心建立了一个全局变量count(第二章解释了这是 如何 发生的),并且它当前的值是NaN。固然,一旦他发现这个不寻常的结果后,他会有一堆其余的问题:“它怎么是全局的?为何它是NaN而不是某个正确的计数值?”。(见第二章)


与停在这里来深究为何this引用看起来不是如咱们 期待 的那样工做,而且回答那些尖锐且重要的问题相反,许多开发者简单地彻底回避这个问题,转向一些其余的另类解决方法,好比建立另外一个对象来持有count属性:

image.png


虽然这种方式确实“解决”了问题,但不幸的是它简单地忽略了真正的问题——缺少对于this的含义和其工做方式上的理解——反而退回到了一个他更加熟悉的机制的温馨区:词法做用域。


注意: 词法做用域是一个完善且有用的机制;我不是在用任何方式贬低它的做用(参见本系列的 "做用域与闭包")。但在如何使用this这个问题上老是靠 猜,并且一般都犯 错,并非一个退回到词法做用域,并且从不学习 为何 this不跟你合做的好理由。


为了从函数对象内部引用它本身,通常来讲经过this是不够的。你用一般须要经过一个指向它的词法标识符(变量)获得函数对象的引用。


考虑这两个函数:

image.png


第一个函数,称为“命名函数”,foo是一个引用,能够用于在它内部引用本身。


可是在第二个例子中,传递给setTimeout(..)的回调函数没有名称标识符(因此被称为“匿名函数”),因此没有恰当的办法引用函数对象本身。


注意: 在函数中有一个老牌儿可是如今被废弃的,并且使人皱眉头的arguments.callee引用 也 指向当前正在执行的函数的函数对象。这个引用一般是匿名函数在本身内部访问函数对象的惟一方法。然而,最佳的办法是彻底避免使用匿名函数,至少是对于那些须要自引用的函数,而使用命名函数(表达式)。arguments.callee已经被废弃并且不该该再使用。


对于当前咱们的例子来讲,另外一个 好用的 解决方案是在每个地方都使用foo标识符做为函数对象的引用,而根本不用this:

image.png


然而,这种方法也相似地回避了对this的 真正 理解,并且彻底依靠变量foo的词法做用域。


另外一种解决问题的方法是强迫this指向foo函数对象:

图片


与回避this相反,咱们接受它。 咱们将会更完整地讲解这样的技术 如何 工做,因此若是你依然有点儿糊涂,不要担忧!


它的做用域

第二常见的对this的含义的误解,是它不知怎的指向了函数的做用域。这是一个刁钻的问题,由于在某一种意义上它有正确的部分,而在另一种意义上,它是严重的误导。


明确地说,this不会以任何方式指向函数的 词法做用域。做用域好像是一个将全部可用标识符做为属性的对象,这从内部来讲是对的。可是JavasScript代码不能访问做用域“对象”。它是 引擎 的内部实现。


考虑下面代码,它(失败的)企图跨越这个边界,用this来隐含地引用函数的词法做用域:

图片


这个代码段里不仅有一个错误。虽然它看起来是在故意瞎搞,但你看到的这段代码,是从公共的帮助论坛社区中被交换的真实代码中提取出来的。真是不可思议对this的臆想是多么的误导人。


首先,试图经过this.bar()来引用bar()函数。它几乎能够说是 碰巧 可以工做,咱们过一下子再解释它是 如何 工做的。调用bar()最天然的方式是省略开头的 this.,而仅对标识符进行词法引用。


然而,写下这段代码的开发者试图用this在foo()和bar()的词法做用域间创建一座桥,使得bar()能够访问foo()内部做用域的变量a。这样的桥是不可能的。 你不能使用this引用在词法做用域中查找东西。这是不可能的。


每当你感受本身正在试图使用this来进行词法做用域的查询时,提醒你本身:这里没有桥。


什么是this?

咱们已经列举了各类不正确的臆想,如今让咱们把注意力this机制是如何真正工做的。


咱们早先说过,this不是编写时绑定,而是运行时绑定。它依赖于函数调用的上下文条件。this绑定和函数声明的位置无关,反而和函数被调用的方式有关。


当一个函数被调用时,会创建一个活动记录,也称为执行环境。这个记录包含函数是从何处(call-stack)被调用的,函数是 如何 被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的this引用。


复习

对于那些没有花时间学习this绑定机制如何工做的JavaScript开发者来讲,this绑定一直是困惑的根源。猜想,试错,或者盲目地从Stack Overflow的回答中复制粘贴,都不是有效或正确利用this这么重要的机制的方法。


为了学习this,你必须首先学习this不是 什么,不管是哪一种把你误导至何处的臆测或误解。this既不是函数自身的引用,也不是函数词法做用域的引用。


this其实是在函数被调用时创建的一个绑定,它指向 什么 是彻底由函数被调用的调用点来决定的。

相关文章
相关标签/搜索