Skip to content

你并不了解 JavaScript:开始 - 第二版

第 4 章:大局观

本书概述了你在入门 JS 时需要注意的事项。我们的目标是填补那些刚接触 JS 的读者在早期接触该语言时可能踩的坑。我也希望我们已经暗示了足够多的更深层次的细节,以激起你的好奇心,想更多地了解这种语言。

在本系列的其他书籍中,我们将会解读语言中所有的其他特性,其详细程度远远超过我们在这里的几个简短章节中所能做到的。

不过,请记住,忙乱源于贪快。与其匆匆忙忙地去看下一本书,试图快速地读完所有的书,不如花些时间回顾一下本书的内容。再花些时间看看你当前项目中的代码,并将你所看到的与到目前为止所讨论的内容进行比较。

当你准备好了,这一章主要将 JS 语言分为三个重要组成部分,然后提供了一个简短的路线图,说明对这一系列书的其余部分的展望,以及我建议你如何前行。另外,不要跳过附录,特别是附录 B「温故而知新」。

重要组成 1: 作用域与闭包

将变量组织成作用域单位(函数、块)是任何语言最基础的特征之一;也许没有其他特征对程序的行为方式有更大的影响了。

作用域就像桶,而变量就像你放进这些桶里的弹珠。一种语言的作用域模型就像帮助你确定哪种颜色的弹珠放在哪种匹配颜色的桶里的规则。

对于作用域之间相互嵌套,任何给定的表达式或语句只有该层作用域嵌套的变量或更高/外层作用域中的变量可以被访问;较低/内层作用域中的变量将被隐藏,无法访问。

这就是作用域在大多数语言中的表现,这被称为词法作用域。作用域单元的边界,以及变量在其中的组织方式,是在程序被解析(编译)时决定的。换句话说,这是由开发者决定的:你在程序中定位一个函数/作用域的位置决定了程序中这一部分的作用域结构是什么。

JS 是词法作用域的,尽管许多人声称它不是,因为它的模型有两个特殊的特点,是其他词法作用域的语言所不具备的。

第一种通常被称为变量提升:当所有在作用域中的任何地方声明的变量都被当作在作用域的开头声明的。另一种是 var 声明的变量是函数作用域,即使它们出现在一个块内。

变量提升和函数作用域的 var 都不足以支持 JS 没有词法作用域的说法。 let/const 声明有一个特殊的错误行为,叫做「暂时性死区」 (TDZ),它导致了可观察但不可用的变量。虽然 TDZ 的出现很奇怪,但它不是词法作用域失效的证明。所有这些都是语言的独特部分,所有 JS 开发者都应该学习和理解。

当语言将函数作为头等公民时,闭包是词法作用域的自然结果,就像 JS 那样。当一个函数引用外部作用域的变量,并且该函数作为一个值被传递并在其他作用域中执行时,它保持对其原始作用域变量的访问;这就是闭包。

在所有的编程语言中,特别是在 JS 中,闭包最重要的编程模式,包括模块化。在我看来,当涉及到 JS 中的代码组织时,模块化是你的神兵利器。

要进一步了解作用域、闭包以及模块化如何工作,请阅读第二册《作用域与闭包》。

重要组成 2: 原型

该语言的第二个重要组成是原型系统。我们在第三章(「原型」)中深入讨论了这个话题,但我只想对它的重要性再做一些强调。

JS 是为数不多可以选择直接和明确地创建对象的几种语言之一,你不需要先在类中定义它们的结构。

多年来,人们在原型之上实现类的设计模式,即所谓的「原型继承」(见附录 A,「『类』的原型」)。然后随着 ES6 的 class 关键字的出现,该语言更加倾向于面向对象 (OO)/Class 式编程。

但我认为这种倾向掩盖了原型系统的美丽和能力:两个对象能够简单地相互连接,并通过共享一个 this 上下文进行动态合作(在函数/方法执行期间)。

类只是你可以建立在这种力量之上的一种模式。但另一种方法,在一个非常不同的方向上,是拥抱简单的对象,完全忘记类,让对象通过原型链来合作。这就是所谓的行为委托。我认为委托比类的继承更强大,是我们在程序中组织行为和数据的一种手段。

但类的继承引人注目。其余的则是函数式编程 (FP),作为设计程序的一种「反类」方式。这让我很难过,因为它扼杀了委派作为一种可行的替代方案的机会。

我鼓励你花大量时间深入阅读第三本书对象与类,看看对象委托的潜力如何远远超过我们也许已经意识到的。这并反对使用「类」,它表达是:「类并不是使用对象的唯一方法」,我希望更多的 JS 开发者思考这个问题。

我想说的是,与类相比,对象更符合 JS 的规律(关于这个问题的细节稍后再说)。

重要组成 3: 类型和强制转换

JS 的第三个重要组成部分是迄今为止 JS 特性中最被忽视的部分。

绝大多数开发者对类型在编程语言中的工作方式有强烈的误解,特别是它们在 JS 中的工作方式。在更广泛的 JS 社区,人们开始对「静态类型」产生兴趣,并使用 TypeScript 或 Flow 等类型感知工具。

我同意 JS 开发者应该更多地了解类型,并且应该更多地了解 JS 如何管理类型转换。我也同意类型意识的工具可以帮助开发者,前提是他们首先获得并使用了这些知识!

但我完全不同意 JS 的类型机制不好这样的结论,我们需要用语言之外的解决方案来掩盖 JS 的类型。我们不必遵循「静态类型」的方式,应该在我们的程序中聪明并扎实地使用类型。当然还有其他的选择,如果你愿意与众人的想法相反,而与 JS 的想法相同的话(再次强调,更多关于这个问题的内容将在后面)。

可以说,这个组成部分比其他两个更重要,因为任何 JS 程序如果不适当地利用 JS 的值类型,以及类型之间的值转换(强制),就没法做得更好。

即使你喜欢 TypeScript/Flow,如果你不深入了解语言本身如何管理值类型,你也不会从这些工具或编码方法中得到最大的好处。

要了解更多关于 JS 类型和强制类型转换的内容,请查看第四册类型与语法。但请不要因为你总是听说我们应该使用 === 而忘记其他细节,就跳过这个话题。

如果不学习这部分,你的 JS 基础是不稳定的,甚至可以说是不完整的。

顺其自然[1]

在继续学习 JS 的过程中,我有一些建议要与大家分享,在这本书系列的其余部分中,你的道路也是如此:要注意细节(回顾本章前面提到的各种细节)。

首先,考虑一下大多数人如何对待和使用 JS 的细节。你可能已经注意到,这些书在很多方面都与「细节」相悖。在 YDKJSY 中,我尊重你这个读者,为你解释 JS 的所有部分,而不仅仅是一些精选的流行部分。我相信你有能力也值得拥有这些知识。

这并不是你从其他资料中可以找到的东西。这也意味着,你越是遵循和坚持这些书中的指导,仔细思考并为自己分析怎么做在你的代码中最好的,你就越能脱颖而出。这可能是一件好事,也可能是一件坏事。成人不自在,自在不成人!

但也有很多人告诉我,他们在求职面试时引用了这些书中的一些话题/解释,而面试官告诉求职者他们错了;甚至据说有人因此而失去了工作机会。

在这些书中,我尽可能地提供关于 JS 的完全准确的信息,这些信息通常来自于规范本身。但我也对你如何在你的程序中解释和使用 JS 以获得最大利益发表了一些看法。我不会把观点当作事实,反之亦然。在这些书中,你总能知道哪个是事实。

关于 JS 的事实,其实是不需要辩论的。要么规范说了什么,要么没有。如果你不喜欢规范中所说的,或者我对它的转述,请向 TC39 提出!如果你在面试时,他们声称你的事实是错误的,那么你可以当场问他们是否可以在规范中查找。如果面试官不重新考虑,那么你就不应该想在那里工作。

但是,如果你选择与我的观点保持一致,你必须保持好奇并时常思考为什么会这样。不要只是鹦鹉学舌,我说什么就是什么。拥有你的观点、为它们辩护。如果你希望与之合作的人不同意,请昂首挺胸地离开。JS 很庞大有很多不同的方向。

换句话说,不要害怕逆行,就像我在这些书和我所有的教义中所做的那样。没有人可以告诉你如何最好地利用 JS;这要由你自己决定。我只是想让你有能力得出自己的结论,无论结论是什么。

另一方面,有一个你真正应该注意和遵循的东西:在语言层面上,JS 是如何工作的细节。在 JS 中,如果有正确的实践和方法,有些事情可以很好地、自然地进行,而有些事情你真的不应该在语言中尝试去做。

你能使你的 JS 程序看起来像一个 Java、C# 或 Perl 程序吗?那 Python 或 Ruby,甚至是 PHP 呢?在不同程度上,你当然可以。但你应该这样做吗?

不,我不认为你应该这样做。我认为你应该学习和接受 JS 的方式,并使你的 JS 程序尽可能的实用化。有些人认为这意味着马虎和非正式的编程,但我完全不是这个意思。我的意思是,JS 有很多模式和熟语是可以被识别为「JS」的,而顺着这个思路走是获得最佳成功的途径。

最后,也许最重要的是要认识到你所工作的现有程序和与你合作的开发人员是如何做事的。不要读了这些书后,就想在一夜之间改变你现有项目中的所有细节。这种方法总是会失败的。

你必须随着时间的推移,一点一点地转变这些东西。努力与你的同事们建立共识,说明为什么要重新审视和重新考虑一种方法。但每次只做一个小话题,让前后的代码比较来做大部分的讨论。让团队中的每个人都来讨论,并推动基于分析和代码证据的决策,而不是「我们的高级开发人员总是这样做」的惰性。

这是我能传授的帮助你学习 JS 的最重要的建议。总是不断寻找更好的方法来使用 JS 赋予我们的东西,来编写更多的可读代码。每一个在你的代码上工作的人,包括你未来的自己,都会感谢你的!

循序渐进

因此,现在你已经对 JS 中剩下的内容有了更广阔的视野,并有了正确的态度来对待你剩下的旅程。

但是在这一点上,我收到的最常见的实际问题之一是,「我应该按照什么顺序读这些书?」有一个直接的答案......但也取决于你自己。

我对大多数读者的建议是按照这个顺序进行这个系列:

  1. 从《入门》(第一册)开始,打下坚实的 JS 基础。好消息是,你已经快读完这本书了!
  2. 在《作用域与闭包》(第二册)中,我们将深入研究 JS 的第一个重要组成部分:词法作用域,它如何支持闭包,以及模块化如何组织代码。
  3. 在《对象与类》(第三册)中,重点讨论 JS 的第二个重要组成部分:JS 的 this 是如何工作的,对象原型如何支持委托,以及原型如何使 class 机制用于面向对象 (OO) 式代码组织。
  4. 在《类型与语法》(第四册)中,我们将解决 JS 的第三个也是最后一个重要组成部分:类型和强制类型转换,以及 JS 的语法如何定义我们的代码编写方式。
  5. 有了三大支柱,《同步与异步》(第五册)接着探讨了我们如何使用流程控制来模拟程序中的状态变化,包括同步(立即)和异步(随时间)。
  6. 该系列以《ES.Next 和未来》(第六册)结束,对 JS 的近期和中期未来进行了展望,包括各种可能在不久之后出现在 JS 程序中的功能。

这就是阅读这套书的推荐顺序。

当然,第二、第三和第四册通常可以按任何顺序阅读,这取决于你对哪一个主题感到最好奇和最适合先探索。即使你认为自己已经掌握了该主题,但我不建议你跳过这三本书中的任何一本,尤其是《类型与语法》,因为你们中的一些人可能会想这么做!

第五册(《同步与异步》)对于深入理解 JS 至关重要,但如果你开始钻研并发现它太难理解了,这本书可以推迟到你对该语言更有经验时再看。你写的 JS 越多(而且越纠结!),你就越会欣赏这本书。所以不要害怕在以后的时间里再来读它。

这个系列的最后一本书,《ES.Next 和未来》,在某些方面是独立的。当然如果你想找一条捷径来扩大你对 JS 的了解,正如我所建议的那样,它可以放在最后阅读,或者在《入门》之后立即阅读。这本书在未来也更有可能得到更新,所以你可能会想偶尔重新看一下。

无论你选择如何阅读 YDKJSY,先看看这本书的附录,特别是练习附录 B「温故而知新」。 事不宜迟,没有什么比写代码更好的学习方法了。


  1. 原文:With the Grain,一种语言比其他语言更自然地支持某些编程风格。您可以在 APL 中编写等价物,但当 APL 被设计为仅用于这种事情而没有显式迭代时,这将是浪费。https://wiki.c2.com/?WithTheGrain. ↩︎