本文为《人人都能读标准》—— ECMAScript篇的前言。我在这个仓库中系统地介绍了标准的阅读规则以及使用方式,并深入剖析了标准对JavaScript核心原理的描述。
我先从一个JavaScript的基础概念 —— 作用域,讲起。
你可以在任何搜索引擎或者技术论坛中搜索「js 作用域」,然后在搜索结果中,查看不同的技术文章对作用域这个概念的定义。你会发现,在不同的作者眼中,作用域似乎都不大一样:
某技术文章1:作用域是据名称来查找变量的一套规则。
某技术文章2:作用域指程序中定义变量的区域,它决定了当前执行代码对变量的访问权限。
某技术文章3:作用域,是指变量的生命周期(一个变量在哪些范围内保持一定值)。
某技术文章4:作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。
…
(不贴出具体链接,是因为有的定义重复出现,我不能确定哪一篇是原创的)
有的作者认为作用域是“一套规则”,有的作者认为作用域是“一个区域”,有的作者认为作用域是“生命周期”,还有的作者认为作用域是一个叫作“可访问性”的抽象概念(这显然是一个对英文资料的生硬翻译)。
造成这种现象的核心原因在于:作用域是一个看不见摸不着的东西,我们只能在执行代码的过程中,感受到他在起作用,但我们从未见过它真正的模样。于是这些作者,只能依据个人开发经验,总结归纳并提炼出他们脑海当中“作用域”的样子。而每个作者不同的经验、不同的视角以及不同的知识储备,最终导致了他们说出来的‘作用域“是五花八门的。
等等,这不就是现实版本的“盲人摸象”吗? 。
我们已经“盲”了太久
另一个典型例子就是“this值的解析”。如果你像上面一样查看this值相关的技术文章,你会发现,这些文章好像一直都在非常贴心地为你总结着各种各样关于this机制的“一般性规律”,但几乎每一种所谓的一般性规律,都伴随着大量他们知道的以及他们不知道的例外情况,用起来就像一台破旧的电视机,三天两头出问题。
举一条广为流传甚至是流传最广的关于this值的“规律”:
this 永远指向最后调用它的那个对象
同样的意思另一个说法是:
函数最后由谁调用,this值就指向谁。
比如,下面的代码中,对象o调用了函数a,所以函数a中的this指向对象o。
const o = {
a: function(){
console.log(this)}
}
o.a() // o
好像没什么问题。一般来说,作者此时会开始“洋洋自得”地使用这条“规律”解释一个关于this值的奇怪现象:
function a(){ console.log(this) } a() // window
作者解释:之所以输出为全局对象window,是因为此时函数a实际上是由window对象调用的,即
window.a()
。
看到这里,你是不是觉得这是一条穿透this值本质的规律?可它实际上并不是!
第一,这条规律不适用于模块代码:
<script type="module">
function a(){
console.log(this)
}
a() //undefined
</script>
第二,这条规律不适用于箭头函数:
const o = {
a: () => {
console.log(this)}
}
o.a() // window
第三,这条规律不适用于严格模式的函数:
function a(){
"use strict"
console.log(this)
}
a() // undefined
第四,这条规律不适用于构造器函数:
const o = {
a: function(){
console.log(this)}
}
new o.a() // 输出即不是window、也不是o,是一个新创建的对象
第五,这条规律不适用于函数有显式绑定的情况:
const o = {
a: function(){
console.log(this)}.bind({
c:1})
}
o.a() // {c:1}
最后,甚至,这条规律中最让作者得意的那一部分,也是错的:
let a = function(){
console.log(this)}
console.log(window.a) // undefined
a() // window
window.a() // 报错:window.a is not a function
我只要使用let来声明函数a,函数a就不会出现在window上,此时直接调用window.a()
是会报错的,但执行函数a的结果依旧输出为window。也就是说,“输出为全局对象”这个事情,跟函数是不是在被全局对象调用根本没有半毛钱关系。 实际上,这是“发明”这条规律的作者所不知道的其他机制在起作用。
也就是说,搞了这么久,这条规律正确的说法应该是:
除了模块代码、箭头函数、严格模式、new表达式、显示绑定、普通调用的情况以外,this永远指向最后调用它的那个对象!
看吧,有时候你也不能怪程序员不好找对象,他们对“永远”的理解跟正常人不太一样。
我们看不见“作用域”,所以我们对作用域的样子众说纷纭;我们看不见this值的底层解析机制,所以我们对其机制的揣测各不相同,且漏洞百出;但有时候,由于我们看不见,在某些地方我们又会达成奇怪的一致,即便是错误的一致。比如,关于执行上下文的类型。
网络上所有的技术文章,关于执行上下文的类型,或者说创建执行上下文的方式,都整齐划一,异口同声 —— 3种,就连最近号称要让前端程序员回家种