前言


临近年末,相信接下来的文章正是你所需要的。本文由前端早读课专栏作者@HetfieldJoe翻译授权原创分享。

ps:如果想看代码,可通过点击图片查看


正文从这开始~


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


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


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


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


注意: “this”这个词是在一般的论述中极常用的代词。所以,特别是在口头论述中,很难确定我们是在将“this”作为一个代词使用,还是在将它作为一个实际的关键字识别符使用。为了表意清晰,我会总是使用this来代表特殊的关键字,而在其他情况下使用“this”或 this 或this。


为什么用 this?

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


让我们试着展示一下this的动机和用途:



如果这个代码段 如何 工作让你困惑,不要担心!我们很快就会讲解它。只是简要地将这些问题放在旁边,以便于我们可以更清晰的探究 为什么。


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


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


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


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


困惑

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


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


它自己

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


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


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


过一会儿我们将探索一个模式,来展示this是如何不让一个函数像我们可能假设的那样,得到它自身的引用的。


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


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


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


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


与停在这里来深究为什么this引用看起来不是如我们 期待 的那样工作,并且回答那些尖锐且重要的问题相反,许多开发者简单地完全回避这个问题,转向一些其他的另类解决方法,比如创建另一个对象来持有count属性:


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


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


为了从函数对象内部引用它自己,一般来说通过this是不够的。你用通常需要通过一个指向它的词法标识符(变量)得到函数对象的引用。


考虑这两个函数:


第一个函数,称为“命名函数”,foo是一个引用,可以用于在它内部引用自己。


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


注意: 在函数中有一个老牌儿但是现在被废弃的,而且令人皱眉头的arguments.callee引用 也 指向当前正在执行的函数的函数对象。这个引用通常是匿名函数在自己内部访问函数对象的唯一方法。然而,最佳的办法是完全避免使用匿名函数,至少是对于那些需要自引用的函数,而使用命名函数(表达式)。arguments.callee已经被废弃而且不应该再使用。


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


然而,这种方法也类似地回避了对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实际上是在函数被调用时建立的一个绑定,它指向 什么 是完全由函数被调用的调用点来决定的。


©著作权归作者所有:来自51CTO博客作者mb5ff9820fd69b3的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 学习C的第三天-函数
  2. SQL今日一题(11):窗口函数
  3. 为什么说 Python 内置函数并不是万能的?
  4. 给你的Excel增加正则处理函数,简直如虎添翼
  5. Python 之父为什么嫌弃 lambda 匿名函数?
  6. Python 函数为什么会默认返回 None?
  7. Python 为什么没有 main 函数?为什么我不推荐写 main 函数?
  8. 学编程这么久,还傻傻分不清什么是方法(method),什么是函数(function)?
  9. 秒懂!图解四个实用的Pandas函数!

随机推荐

  1. arcgis for android 开发的导航的部分 请
  2. Android 如何清空 Canvas 清屏只需三句
  3. Android:创建常见对话框以及使用对话框实
  4. 4.腾讯微博Android客户端开发——获取未
  5. 如何为sqlite操作构建更有效的设计
  6. 检测电池电量和充电状态
  7. 【Android端ANR卡顿检测】BlockCanary检
  8. GUVCview-Ubuntu下视频录像更简单
  9. Android中Intent详解(一)
  10. Eclipse,Android,Scala变得简单但仍然无效