this 之谜揭底:从浅入深理解 JavaScript 中的 this 关键字(一)
前言
系列首发于公众号『前端进阶圈』 ,若不想错过更多精彩内容,请“星标”一下,敬请关注公众号最新消息。
this 之谜揭底:从浅入深理解 JavaScript 中的 this 关键字(一)
为什么要用 this
考虑以下代码:
这段代码再不同的上下文对象(me 和 you) 中重复使用函数 identify() 和 speak(), 不用针对每个对象编写不同版本的函数。
若不使用 this 如下代码:
消除对 this 的误解
在解释下 this 到底是如何工作的,首先必需消除对 this 的错误认识。
指向自身
为什么需要从函数内部引用函数自身呢?
最常见的原因是递归。
其实 this 并不像我们所想的那样指向函数本身。
考虑以下代码:
先思考,后查看<details><summary>查看答案</summary><pre>
</pre></details>
当执行 foo.count = 0; 时,的确向函数对象 foo 中添加了一个属性 count, 但是函数内部代码中 this.count 中的 this 并不是指向那个函数对象,虽然属性名相同,跟对象却并不相同,困惑随之产生。
如果你会有 “如果我增加的 count 属性和预期的不一样,那我增加的是那个 count?”疑惑。实际上,如果你读过之前的文章,就会发现这段代码会隐式地创建一个全局变量 count。它的值为 NaN。如果你发现为什么是这么个奇怪的结果,那你肯定会有 “为什么它的值是 NaN, 而不是其他值?” 的疑惑。(原理参考:https://mp.weixin.qq.com/s/H1gpn0vfmUwrglMZwk2gzw)
当然也有方法对上述代码进行规避:
虽然从某种角度来说,解决了问题,但忽略了真正的问题——无法理解 this 的含义和工作原理,上述代码而是返回了舒适区——词法作用域。
上面提到的如果匿名函数需要引用自身,除了 this 还有已经被废弃的
arguments.callee
来引用当前正在运行的函数对象。对于上述提到的代码,更进阶的方式就是使用 foo 标识符来替代 this 来引用函数对象,如下代码:
这种解决方式依然规避了 this 问题,并且完全依赖于变量 foo 的词法作用域。
更进阶的方式是强制 this 指向 foo 函数对象, 使用
call, bind, apply
关键字来实现。
它的作用域
常见的误解:this 指向函数的作用域,其实在某种情况下是正确的,但在其他情况下是错误的。
其实,this 在任何情况下都不指向函数的词法作用域。
考虑一下代码:
先思考,后查看<details><summary>查看答案</summary><pre>
</pre></details>
首先,这段代码试图通过 this.bar() 来引用 bar() 函数。这是绝对不可能成功的,我们之后会解释原因。调用 bar() 最自然的方法是省略前面的 this,直接使用词法引用标识符。
此外,编写这段代码的开发者还试图使用 this 联通 foo() 和 bar() 的词法作用域,从而让 bar() 可以访问 foo() 作用域里的变量 a。这是不可能实现的,你不能使用 this 来引用一个词法作用域内部的东西。
this 到底是什么
说了这么多,那 this 到底是一个什么样的机制呢?
之前我们说过 this 是在运行时进行绑定的,而不是在编写时绑定的,它的上下文取决于函数调用时的各种条件。
this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用是,会创建一个执行上下文,这个执行上下文汇总会包含函数在哪里被调用(也就是调用栈),函数的调用方法, 传入的参数等信息。而 this 就是这样一个属性,会在函数执行的过程中被用到。
小结
学习 this 的第一步要明白 this 既不指向函数自身也不指向函数的词法作用域。
this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。
特殊字符描述:
问题标注
Q:(question)
答案标注
R:(result)
注意事项标准:
A:(attention matters)
详情描述标注:
D:(detail info)
总结标注:
S:(summary)
分析标注:
Ana:(analysis)
提示标注:
T:(tips)
往期推荐:
最后:
版权声明: 本文为 InfoQ 作者【控心つcrazy】的原创文章。
原文链接:【http://xie.infoq.cn/article/a79eff8e91130e3ce8aa73d88】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论