本书讲述如何指挥计算机开展工作。如今计算机与螺丝刀一样普遍,但它们比螺丝刀复杂得多,让它们完成你想做的工作并不容易。
如果是一个常见的、易于理解的任务,例如显示你的电子邮件或充当计算器,你可以打开相应的应用程序并开始工作。但对于独一无二或开放式的任务,可能没有现成的应用程序可用。
这就是编程的用武之地。编程是构建程序的行为,而程序是一组告诉计算机该做什么的精确指令。因为计算机是头脑简单、死板的,编程基本上是单调乏味和令人沮丧的。
幸运的是,如果你能克服这些,甚至可能享受愚笨的机器可以处理的严谨思维,那么编程可以带来丰厚的回报。它允许你在几秒钟内完成手工永远无法完成的操作。这是一种让你的计算机工具做以前无法做的事情的方法。它提供了抽象思维的精彩练习。
大多数编程活动都是用编程语言完成的。编程语言是用于指导计算机的人造语言。有趣的是,我们发现与计算机通信的最有效方式是从人类彼此通信的方式中借鉴大量的内容。与人类语言一样,计算机语言允许以新的方式组合单词和短语,从而可以表达新的概念。
基于语言的界面,例如20世纪80年代和90年代的BASIC和DOS提示符,在某种程度上曾经是与计算机交互的主要方法。它们在很大程度上被图形界面取代,图形界面更容易学习,但提供的自由度更低。如果你知道计算机语言的藏身之处,就仍然可以把它们用起来。每种现代Web浏览器都内置了一种这样的语言——JavaScript,因此它几乎可以在每台设备上使用。
本书将努力让你熟悉这种语言,以便用它来做有用和有趣的事情。
关于编程
除了解释JavaScript之外,我还将介绍编程的基本原理。事实证明,编程很难。基本规则简单明了,但基于这些规则构建的程序往往变得非常复杂,从而无法说明其规则和复杂性。编程在某种程度上构建了自己的迷宫,你可能会迷失在里面。
有时读这本书会让人非常沮丧。如果你不熟悉编程,那么会有很多新内容需要消化。其次,大部分时候都需要你具备其他相关知识才能理解这些内容的组合。
你需要付出必要的努力。当你努力学习这本书时,不要对自己的能力有任何怀疑。你很优秀——只需要坚持下去。休息一下,重读一些章节,并确保阅读并理解示例程序和练习。学习是一项艰苦的工作,但你学到的一切都属于自己,并且会使后续的学习变得更容易。
当行动变得无利可图时,收集信息;
当信息变得无利可图时,睡觉。
—Ursula K. Le Guin,《黑暗的左手》
程序这个词有多重含义。它是由程序员键入的一段文本,是使计算机完成任务的指挥力量,它也是计算机内存中的数据,它还控制在同一内存上执行的操作。将程序与我们熟悉的对象进行类比往往不尽如人意。有一种表面上比较恰当的比喻,即把程序视作包含许多零件的机器,为了使整台机器正常运转,我们必须考虑如何将这些零件相互连接起来,并实现整体的运转。
计算机是一台承载这些无形机器的物理机器。计算机本身只能做简单直接的事情。它们如此有用的原因是它们做这些事情的速度极快。程序可以巧妙地结合大量简单的动作来完成非常复杂的事情。
程序是思想的结晶。构造它不需要成本,它也没有重量,通过打字我们很容易把它创造出来。
但是,如果不加注意,程序的大小和复杂性将会失去控制,甚至会使构造它的人无法理解。保持程序受控是编程中需要考虑的主要问题。当一个程序正常工作时,它是优美的。编程艺术是控制复杂性的技能。出色的程序都是简明的,它们的复杂性都不太高。
一些程序员认为管理这种复杂性的最好办法是只在程序中使用一小部分易于理解的技术。他们制定了严格的规则(“最佳实践”)来规定程序应该具有的形式,并小心地把它们限定在很小的安全范围内。
这不仅无聊,而且效果不佳。新问题通常需要新的解决方案。编程的历史不长,并且仍在迅速发展,而且它的变化足以为不同的方法提供空间。在程序设计中有很多可怕的错误,应该继续大胆犯错以加深你的理解。编写好程序的感觉是在编程实践中培养起来的,而不是从一系列规则中学到的。
. 为什么语言很重要
在计算机诞生之初,没有编程语言。程序看起来像是这样的:
这是一个将数字从1到10加在一起并打印出结果的程序:1 + 2 + … + 10 = 55。它可以运行在一台简单的虚拟机器上。要对早期的计算机进行编程,必须在正确的位置设置大型开关阵列,或者在纸带上打孔并将它们送入计算机。你可以想象编写这种程序是多么乏味和容易出错。即使编写简单的程序也需要很多聪明才智和规则。编写复杂的程序几乎是不可思议的。
当然,手动输入这些神秘的二进制位(1和0)模式确实给程序员一种强烈的成就感,仿佛自己成了魔法师。在工作满意度方面,这一定非常令人满足。
先前程序的每一行都包含一条指令。它可以用如下中文表达:
(1)将数字0存储在内存位置0中。
(2)将数字1存储在内存位置1中。
(3)将内存位置1的值存储在内存位置2中。
(4)从内存位置2的值中减去数字11。
(5)如果内存位置2的值为数字0,则继续执行指令9。
(6)将内存位置1的值添加到内存位置0。
(7)将数字1添加到内存位置1的值中。
(8)继续执行指令3。
(9)输出内存位置0的值。
虽然这已经比一堆二进制位的可读性更好,但它仍然相当难懂。用名称代替数字来指代指令和内存位置会有所改善。
你能看出这个程序是如何运作的吗?前两行为两个内存位置提供了它们的起始值:total将用于构建计算结果,count将跟踪我们当前正在查看的数字。使用compare的行可能是最奇怪的。此程序想要查看count是否等于11来决定它是否可以停止运行。因为我们的虚拟机器相当原始,它只能测试一个数字是否为零并据此做出决定。因此,它使用标记为compare的内存位置来计算count - 11的值,并根据此值来做决策。接下来的两行将count的值添加到结果中,并且每当程序确定count不是11时,把count增加1。
下面是JavaScript中的相同程序:
这个版本为我们提供了一些改进。最重要的是,没有必要再次指定程序来回跳转的方式。while结构负责这一点。只要它给出的条件成立,它将继续执行它下面的代码块(用括号括起来的部分)。条件是count 在程序结束处,在while结构完成之后,console.log操作用于写出结果。
最后,如果我们正好有方便的range和sum操作可用,可以分别创建一个范围内的数字集合并计算数字集合的总和,例如下面的这个程序:
这个故事的寓意是,同一个程序的表达方式可长可短,可读性有好有坏。程序的第一个版本非常晦涩难懂,而最后一个版本几乎是一句直白的话:记录(log)从1到10的数字范围(range)内的总和(sum)。(我们将在后面的章节中看到如何定义像sum和range这样的操作。)
一种好的编程语言可以帮助程序员讨论计算机必须在更高级别执行的操作。它有助于省略细节,提供方便的构件(例如while和console.log),允许你定义自己的构件(例如sum和range),并使这些构件易于组合。
什么是JavaScript
JavaScript于1995年面世,它是在Netscape Navigator浏览器中向网页添加程序的一种方式。所有其他主要的图形Web浏览器都已采用了此语言。它使现代Web应用程序成为可能——你可以直接与这种应用程序交互,而不必为每个动作重新加载页面。JavaScript也用于更传统的网站,以提供各种形式的交互性和智能。
值得注意的是,JavaScript与名为Java的编程语言几乎无关。采用相似名称是出自营销考虑,而不是合理的判断。当JavaScript出现时,Java语言正在大规模推广并且越来越受欢迎。有人认为尝试搭乘这一成功语言的顺风车是一个好主意。现在我们已经无法摆脱这个名字了。
JavaScript语言在Netscape之外得到采用之后,人们编写了一个标准文档来描述它的工作方式,以便声称支持JavaScript的各种软件实际上都在讨论同一种语言。这被称为ECMAScript标准,得名于标准化它的ECMA国际组织。在实践中,ECMAScript和JavaScript这两个术语可以互换使用,它们是同一种语言的两个名称。
有人会说JavaScript有许多糟糕之处。其中很多都是确实存在的。当我第一次被要求用JavaScript编写程序时,我很快就开始鄙视它了。它几乎可以接受我输入的任何代码,但它解释这个代码的方式却与我的原意完全不同。这与我当时没有弄清楚“我在做什么”有很大关系,但这里有一个真正的问题:JavaScript在它允许的内容上过于自由。这种设计背后的初衷是,它将使初学者更容易使用JavaScript进行编程。实际上,因为系统不会向你指出问题,所以更难以在程序中发现问题。
不过,这种灵活性也有其优点。它为许多技术留下了空间,这些技术在更严格的语言中是不可能实现的,正如你将看到的(例如在第10章中),它的灵活性可以用来克服一些JavaScript的缺点。在正确学习JavaScript语言并花一段时间使用它后,我真的变得喜欢它了。
JavaScript有好几个版本。ECMAScript第3版是JavaScript在2000年到2010年逐渐占据主导地位时得到广泛支持的版本。在此期间,ECMA正在雄心勃勃地开发第4版,此版本计划对该语言进行一些彻底的改进和扩展。以这种激进的方式改变一种活生生的、广泛使用的语言,在现实中非常困难,所以ECMA在2008年放弃开发第4版,这导致改动远不那么激进的第5版。第5版于2009年发布,只进行了一些无争议的改进。然后在2015年发布了第6版,这是一个重大更新,其中包括原计划加到第4版中的一些想法。从那时起,我们每年都有新的小更新。
语言不断发展这一事实意味着浏览器必须不断跟进,如果你使用的是较旧的浏览器,则可能无法支持所有功能。语言设计人员谨慎地不做任何可能破坏现有程序的更改,因此新浏览器仍然可以运行旧程序。在本书中,我使用的是2017版JavaScript。
Web浏览器不是唯一使用JavaScript的平台。一些数据库(如MongoDB和CouchDB)也使用JavaScript作为脚本和查询语言。用于桌面和服务器编程的几个平台为在浏览器之外编写JavaScript提供了环境,其中最值得注意的是Node.js项目(详见第20章)。
代码以及对它的处理方式
代码是组成程序的文本。本书中的大多数章节都包含很多代码。我相信阅读代码和编写代码是学习编程不可或缺的部分。不要只是粗略浏览一下这些例子,务必仔细阅读并理解它们。这么做起初可能很慢而且费脑子,但我保证你会很快掌握它。习题也是如此。在你真正编写出有效的解决方案之前,不要认为已经明白了它们。
我建议你尝试使用实际的JavaScript解释器来检查习题答案。利用这种方式,你可以立即获得你的代码是否有效的反馈,并且,我希望你会尝试进行实验,并且不只限于这些习题。
运行本书中的示例代码并进行实验的最简单方法是在https://eloquentjavascript.net上的本书在线版本中查找。在那里,你可以单击任何代码示例来编辑和运行它,并查看它产生的输出。要进行练习,请访问https://eloquentjavascript.net/code,它为每个编码习题都提供启动代码,并允许你查看解决方案。
如果你想在本书的网站之外运行书中定义的程序,则需要注意一些事项。许多示例都独立存在,应该适用于任何JavaScript环境。但是后面章节中的代码通常是针对特定环境(浏览器或Node.js)编写的,并且只能在那种环境中运行。此外,许多章节定义了规模更大的程序,出现在它们中的代码片段彼此依赖或依赖于外部文件。网站上的沙盒提供了Zip文件的链接,其中包含运行给定章节代码所需的所有脚本和数据文件。
本书概述
本书包含三个部分。前十二章讨论了JavaScript语言。接下来的七章介绍Web浏览器以及JavaScript用于浏览器编程的方式。最后,用两章专门介绍Node.js,这是另一种用于编写JavaScript的环境。
在整本书中,有五个项目实战章节,它们描述了大型的示例程序,让你体验实际的编程。按照出现顺序,我们将建立一个投递机器人、一种编程语言、一个平台游戏、一个像素绘图程序和一个动态网站。
本书语言部分的前四章介绍JavaScript语言的基本结构。它们引入了控制结构(如while)、函数(编写自己的构件)和数据结构。在这之后,你将能够编写基本的程序。接下来,第5章和第6章介绍了使用函数和对象技术编写更抽象的代码并控制复杂性。
在第一个项目实战章节之后,本书的语言部分继续介绍错误处理和错误修复、正则表达式(用于处理文本的重要工具)、模块化(针对复杂性的另一种防御)和异步编程(处理花时间的事件)等内容。第二个项目实战章节总结了本书的第一部分。
第二部分为第13~19章,描述了浏览器JavaScript可以访问的工具。你将学会在屏幕上显示内容(第14章和第17章)、回应用户输入(第15章),以及通过网络进行通信(第18章)。本部分也包括两个项目实战章节。
第三部分为第20~22章。第20章描述了Node.js,第21章使用此工具构建了一个小型网站。最后,第22章描述了在优化JavaScript程序以提高速度时需要注意的一些事项。
印刷约定
在本书中,用等宽(monospaced)字体书写的文本将代表程序的组成元素——有时它们是自给自足的片段,有时它们只是指附近程序的一部分。程序编写如下:
有时,为了显示程序产生的输出,会在程序代码之后写出预期的输出,用前面的两个斜杠和一个箭头来标识。
祝你好运!