第 0 章 导读 ○ ○ ○ ○ ○
随机事件之繁复,以及一猿执笔于案前能写何字,凡此种种皆不可控。
——George Marsaglia
JavaScript并不完美,但是这不妨碍它运行起来。
本书的受众有两类:一是有一定JavaScript基础并且想更深刻地理解其内在逻辑和用法的读者,二是有一定编程经验并且想了解一门新语言内在逻辑的读者。
也就是说,其实本书并不适合初学者阅读。期待将来的某一天,我也可以专门为初学者写一本书。但这本不是,毕竟它有一定的深度。如果你仅略读本书,可能收获甚微。
本书不会讲解JavaScript引擎或者虚拟机,而会讲解JavaScript这门语言本身,以及每一位JavaScript开发人员都需要明确的事情。本书可能会让你重新认识JavaScript,包括它是如何运作的、怎样让它变得更优秀,以及如何更好地使用它。本书还会教你如何正确地看待JavaScript,以及如何正确地用JavaScript进行思考。在本书中,我会依照ES6版本来讲解,并不会赘述ES1、ES3以及ES5等版本的细节。这些内容实际上并不重要,毕竟我们只需要关注当下的JavaScript就好了。
本书并不面面俱到,很多内容并未讨论。如果我在书中没有提到你关注的一些特性,那很有可能是因为它们设计得太糟糕了。还有,我不会花很多篇幅探讨语法本身,毕竟大多数人对JavaScript的语法多少还是有一些了解的。如果你在语法等内容上需要帮助,可以参考JSLint网站上的相关内容。
我会在书中稍微提及JavaScript中比较有用的一些部分,例如原型(prototype)上的大多数内置方法。有一些在线精品资料库也列出了这些内容。我个人极力推荐Mozilla基金会的资料库。
编程语言的重要设计目标之一就是尽可能使其简洁、优雅、逻辑性强,没有各种奇怪的极端情况。然而事实上,JavaScript远没有达到这个目标。随着越来越多的特性加入,每一次新版的发布都会使其变得越来越糟糕。现在这门语言充满了各种奇怪的用法和边界情况。本书会稍微提及这些奇怪的用法,以告诉大家其中潜藏着可怕的“怪物”。我们应当远离这些东西,尽量待在这门语言干净阳光的一面,这里已经有能让你写出好程序所需的一切了,不要让自己堕入无边黑洞。
10年前,我写过一本关于JavaScript的神奇小手册。虽然JavaScript表面一团糟,但其内在仍然是美好的。通过避其糟粕,你可以写出出色的JavaScript代码。这一点与某些编程专家的观点相悖,他们认为精通一门语言的所有特性才能证明自己造诣高深。对于他们而言,特性就是用于掌握和精通的、不容辩驳,自然也就根本不存在糟糕的特性。这种观点显然是错的,但很遗憾,目前它仍占据着主导地位。
事实上,真正的“精通”应该体现在代码的可读性、可维护性以及是否无错上。如果你做到了这几点,那就真的可以炫耀了。做一个谦逊的程序员吧。吾日三省吾身:自身可乎?工作可乎?可有提升乎?经验之谈,为炫技而过分使用各种特性,只会适得其反。
这是我用来提升自己所写代码的“不传之法”:
如果一个特性时而有用,时而是个“坑”,并且有更好的选项,那么我们就应该始终选择那个“更好的选项”。
也就是说,对于一门语言来说,我使用的一直是它能满足我的“最小集”,这样就能避免使用那些可能有“坑”的特性。这个对于我自己而言的“最小集”也并非一成不变,我一直在完善它。本书就记录了我到目前为止对于JavaScript的相关思考。我之所以还能写一些JavaScript的优点,是因为JavaScript确实有不少可取之处。虽然相较于10年前,JavaScript的精粹变少了,但留下来的那些精粹更显闪耀。
近年来,JavaScript已经成了世界上最重要的编程语言之一。说来惭愧,我应对此负部分责任,在此先给读者道个歉。多个新版ECMAScript规范的出台并没能解决JavaScript自身深层次的问题,反而创造了更多的问题。实际上,标准委员会修复问题的权力有限,让这门语言野蛮发展的权力反倒大得很,放任其复杂性和怪异性一再增加。要是阻碍了JavaScript的发展(哪怕是往糟糕的方向发展),那么他们的乐趣何在?
人们不停地给老化的语言“整容”,拼命地往其中注入各种新的特性来稳住其流行地位,或者至少让其看起来不那么“圡”1。与“代码膨胀”一样,“特性膨胀”过犹不及。我们更应该去发现JavaScript的内在美,而不是做各种表面功夫。
1该字古同“土”,由于外形看起来像“土到掉渣”,因此现在有时用于形容老掉牙。——译者注
我推荐你阅读ECMAScript规范。它虽然读起来可能有些晦涩,但好在是免费的。
说实话,阅读ECMAScript规范在一定意义上改变了我的人生。跟大多数开发人员一样,我在使用JavaScript之前并没有去系统地学习它。正因为如此,我当时认为这门语言很烂——各种运行行为令人困惑,就是让人喜欢不起来。直到有一天阅读了ECMAScript规范,我才发现JavaScript的绝妙之处。
0.1 异类
我有预感,本书会让一些同僚感到不舒服。我是异类,正在挑战一些守旧者的权威。我已经习惯这些了。多年前,我因为发现了JavaScript居然有精粹并将其整理成册而饱受挑战和攻击。还有当我刚提出JSON(它现在已经成了时下最流行的数据交换格式)的时候,也是如此。
社区是有信仰的,哪怕这些信仰存在错误,社区成员也能从中获益。因此,当信仰被人质疑时,社区成员就会觉得受到了威胁。对,我就是这个质疑的人。我对真理的渴求高于对社区利益的看重。恰恰就是这一点会让很多人不高兴。
我其实只是一个普通程序员,只想找到一个最佳实践来写出优美的代码。虽然我的一些想法可能不对,但我也在思考如何纠正这些想法。我们这代程序员有很多思维模式已在FORTRAN时代固化,我觉得是时候踏出改变的一步了。不过,即使我处在一个极具创造性的行业中,变革仍然并非易事。
如果你认为自己被我这个异类的话冒犯了,那么我建议你将本书放回书架并远远走开。
0.2 代码
本书的所有随书代码都可以免费获取。你可以将其用于任何目的,但请不要拿它们“作恶”。如果有可能,我希望这些代码能让你做一些“好事”。
强烈建议你不要简单地复制粘贴你并不理解的那些代码。虽然我们经常戏称自己是“复制粘贴工程师”,但这种做法实际上是很不可取的。这虽然比不上看都不看一眼就去安装一款未知软件那么蠢,但也实在算不上一种明智之举。在当前的安全技术水平下,最好的安全过滤器就是你的大脑,请务必善用。
虽然我的代码并不完美,但我认为跟我前几年写的代码相比,它们至少还是有进步的。我个人着重在为这方面的进步而努力,并且希望能活到让我的代码达到完美的那一天。我希望你也能在这方面下功夫。你可以在本书的网站(How JavaScript Works)上查看勘误表(erratums)2。在拉丁语中,erratum的复数形式是errata,但谁让我用的是现代英语呢?在现代英语中,我们应该通过添加s或者es来构成复数形式,所以这里我用了erratums。如果要在保持传统和与时俱进之间选择,我选择与历史的车轮一起前进,以此来使世界更美好。
2要查看或提交中文版勘误,请访问图灵社区本书页面。——编者注
如果你发现了本书中的错误,请将勘误发送至邮箱erratum@howjavascriptworks.com。谢谢。
0.3 未来
虽然本书的主题是JavaScript,但有时候我实际上是在讲另一种可以取代JavaScript的语言。我坚信在JavaScript之后应该有一门语言脱颖而出。如果JavaScript是值得学习的最后一门语言,就真的太可悲了。我们应该为子孙后代找到这样的下一门语言。这将是我们留给他们的珍贵宝藏。
我认为未来属于孩子们,也属于机器人。
当下和未来的互联网需要下一代的编程范式,它应当是全局分布式的、安全的和事件化编程的。遗憾的是,当下包括JavaScript在内的几乎所有编程语言依旧停留在旧的范式中,即本地化的、不安全的和顺序化编程的。我把JavaScript看作一门过渡的语言。在JavaScript中使用最佳实践可以很好地为我们未来理解新的编程范式做好准备。
0.4 语法
我认为1的英文拼写是错误的,因此在书中用了自认为更正确的拼写——wun。one这个单词根本不符合任何发音规则,包括各种特殊规则。此外,用一个看着像0的字母作为表示1的单词的首字母,本身就不合适。
不过,wun这个单词对于大众来说,看起来有点奇怪。之所以在书中采用这样的拼写,是因为我想通过此事让你明白一个道理:对陌生事物产生的奇怪感觉并不能证明它是错的。
单词拼写已然发生变革。例如,有些小家伙认为把through拼写成thru会更好,因为他们觉得这个常用单词有一半字母不发音毫无道理,用起来效率低下,也给学生造成了困惑。拼写改革实际上是一次传统与理性的对抗,有时候理性更容易获胜。编程语言亦如此。如果你也觉得wun比one更有意义,那么请和我一起努力吧。
一般人在提到像1到10这类范围的时候,通常将其理解为到10为止,而程序员则通常认为10是被排除在外的。这是由一些编程习惯造成的,比如在编程中起始编号一般是0而不是1。因此,我用“到”(to)来表示程序员日常认为的“到”,而用“过”(thru)来表示普通人认为的“到”。也就是说,“0到3”代表0、1、2,而“0过3”则代表0、1、2、3。简而言之,“到”的语义为小于(<),而“过”则代表小于等于(≤)。
0.5 示例
我喜欢用正则表达式。然而,正则表达式其实是比较晦涩难懂的。我会在正则表达式中加入一些空白,使其看起来更规整易懂。实际上,JavaScript并不支持这样规整的写法。因此,你看到的如下代码:
const number_pattern = / ^ ( -? \d+ ) (?: \. ( \d* ) )? (?: [ e E ] ( [ + \- ]? \d+ ) )? $ /;
在实际中则应该是这样的:
const number_pattern = /^(-?\d+)(?:\.(\d*))?(?:[eE]([+\-]?\d+))?$/;
我实在忍不住在上面晦涩的正则表达式中加入了各种缩进和空格,好让读者读起来一目了然。
在很多章节中,我会使用JavaScript表达式作为示例。通常,我会以一个不以分号(;)结尾的表达式来进行展示,后跟一句注释(以//开头)来表示其结果。
// 示例 3 + 4 === 7 // true NaN === NaN // false typeof NaN // "number" typeof null // "object" 0.1 + 0.2 === 0.3 // false 3472073 ** 7 + 4627011 ** 7 === 4710868 ** 7 // true
上述种种,终焉之前,皆有所释。