简介

我们认为是在以自己的意愿来创建这个系统。但是计算机却不完全像我们,它只是我们自身很小一部分的映射:关于逻辑、次序、规则和明确性的那部分。

— Ellen Ullman, Close to the Machine: Technophilia and its Discontents

这是一本关于指挥计算机的书。现如今,计算机就像螺丝刀一样常见,但却复杂的多,使得指挥它们做你想让它们做的事情不那么容易。

如果你给计算机的任务比较常见,比如:显示出你的 Email 或当作计算器来使用,你可以打开相应的应用来完成工作。但是对于特别的或是灵活的任务,则可能没有这样的一个应用。

这个时侯,编程就能派上用场了。编程指的是组成程序(一组精确的指令,用来告诉计算机要做什么)的操作。因为计算机不会说话而又非常严苛,编程往往比较乏味且让人备受挫折。

幸运的是,如果你弄明白了原理,甚至享受以机器所能处理的语言来进行思考的严谨过程,编程会非常有好处。它可以让你分秒之间完成也许纯手工永远也无法完成的任务。它是一种让你的计算机做以前做不到的事情的一种方式。另外,它也是一种非常棒的抽象思维的练习。

大多数编程借助于编程语言来完成。一门编程语言是人为构建的用来指挥计算机的语言。有趣的是,我们发现与计算机交流的最有效方式极大地借用了我们彼此交流的方式。类似于人类语言,计算机语言允许单词和词组以新的方式进行组合,这就使得表达新的概念成为可能。

像八、九十年代的 BASICDOS 命令行这样基于语言的界面,一度成为了与计算机交互的主要手段。而这些已经被更易学习(但提供较少自由度)的可视化界面所取代。计算机语言仍然存在,只要你知道去哪儿查看。JavaScript 就是这样的语言之一,它被所有现代 Web 浏览器内建,所以几乎每一部设备上都能找到它。

本书将尝试让你熟悉这门语言,并用它来做有用且有趣的事情。

关于编程

除了解释 JavaScript,我还会介绍编程的基本原则。编程,实际上很难。基本原则很简单、清晰,但是基于这些规则所创建的程序往往变得足够复杂而引入了它们自己的规则和复杂度。你以某种方式自行创建了一个迷宫,而你自己也可能迷失在其中。

阅读本书也许经常会让你感到非常沮丧。如果你是编程新手,那么会有很多新材料需要理解。其中的大部分材料将以需要你进行更多连接的各种方式继续组合。

根据你的情况来做必要的功课。当你跟着本书学习很吃力时,不要怀疑你自身的能力。你很好,只需要坚持下去。休息一下,重读某些材料,以确保你阅读和理解了示例程序和练习。学习是很“苦”的事情,但学到的东西就是你自己的了,而且会让后续的学习更加容易。

程序包含很多内容。它是程序员所敲的字符,是让计算机做事的引导力,是计算机内存中的数据,它还控制着在同一块内存上所执行的操作。能把程序和我们熟知的物体相类比的例子还比较少。一个差不多合适的类比,是把程序看作机器:有很多易于组装的零件,为了让整个机器运转,我们必须考虑零件间互联以及整体运转的方式。

一台计算机是一台担任这些无形机器(即:程序)的物理机器。计算机本身只能做简单直接的事情。它们之所以如此有用,是因为它们做事的速度难以置信地快。程序能巧妙地组合巨大数量的简单操作,以完成极其复杂的事情。

一个程序,就是思维的构造过程。它不花什么钱,没有重量,而且易于扩展。

但如果不小心,程序的体积和复杂度将变得失控,甚至让程序创作者也感到困惑。让程序处于控制之下,是编程的主要问题。当程序工作时,是很美妙的。编程的艺术就是控制复杂度的技巧。好程序是在控制中的,能化繁为简。

一些程序员认为最好在程序中仅用少量的熟知的技术来管理这个复杂度。他们制定了严格的规则(“最佳实践”)来规定程序应有的格式,并只在那极小的安全区域内活动。

这不仅无趣,也很低效。新的问题往往需要新的解决方法。编程这个领域还很年轻,还在飞速发展,且百花齐放,极其不同的方法都能被接受。在程序设计中会有很多“可怕”的错误,你应该去做去犯错,以便理解它们。优秀程序的感觉是在实践中培养的,而不是从一堆规则中。

为什么语言很重要

一开始,在计算机诞生之初,还没有编程语言。当时的程序看起来像这样:

  1. 00110001 00000000 00000000
  2. 00110001 00000001 00000001
  3. 00110011 00000001 00000010
  4. 01010001 00001011 00000010
  5. 00100010 00000010 00001000
  6. 01000011 00000001 00000000
  7. 01000001 00000001 00000001
  8. 00010000 00000010 00000000
  9. 01100010 00000000 00000000

这个程序把从 1 到 10 的数字加起来,并打印出结果:1 + 2 + ... + 10 = 55。它可以运行在一台简单的虚拟机器上。为了在早期计算机上编程,你需要在合适的位置设置大批的开关,或者在纸板上打孔然后送入计算机。你可以大概想象一下这个过程多么的单调和易错。即使是写简单的程序,也需要极高的灵巧和自律。对于复杂的程序,更是不可想象。

当然,手动输入这些神奇的比特(即:0 和 1)模式确实让程序员强烈感受到作为一位专家是多么地强大。这就跟职业满意度有关了。

上述程序的每一行包含着一条指令。可以用英语写成这样:

  1. 1. Store the number 0 in memory location 0.
  2. 2. Store the number 1 in memory location 1.
  3. 3. Store the value of memory location 1 in memory location 2.
  4. 4. Subtract the number 11 from the value in memory location 2.
  5. 5. If the value in memory location 2 is the number 0, continue with instruction 9.
  6. 6. Add the value of memory location 1 to memory location 0.
  7. 7. Add the number 1 to the value of memory location 1.
  8. 8. Continue with instruction 3.
  9. 9. Output the value of memory location 0.

尽管这样已经比大量的比特串易读多了,但仍然比较费解。使用名称而不是数字来表示指令和内存地址,就好得多了:

  1. Set total to 0.
  2. Set count to 1.
  3. [loop]
  4. Set compare to count”.
  5. Subtract 11 from compare”.
  6. If compare is zero, continue at [end].
  7. Add count to total”.
  8. Add 1 to count”.
  9. Continue at [loop].
  10. [end]
  11. Output total”.

此时,你能明白这个程序是如何工作的吗?开头两行给两个内存地址设置了初始值:total 用于存储计算的结果,count将用来追踪当前所关注的数字。使用compare的那些行可能是最奇怪的。程序想看看count是不是等于 11,以决定程序是否应终止运行。我们的虚拟机器相当简单,它只能测试一个数字是否为 0,并基于此来做决定。因此,它用标签为compare的内存地址来计算count - 11的值,并根据这个值来做决策。接下来的两行把count的值加到结果中,并且在每次程序判定count还不是 11 时给count加 1。

这里是相同程序的JavaScript版本:

  1. let total = 0, count = 1;
  2. while (count <= 10) {
  3. total += count;
  4. count += 1;
  5. }
  6. console.log(total);
  7. // → 55

这一版有诸多改进。最重要的是,不再需要指定程序来回跳转的方式了,while结构会处理的。只要条件满足,它就会持续执行它下面的块(包裹在大括号中的部分)。这里的条件是count <= 10,意思是“count小于或等于 10”。也不再需要创建一个临时的值,并用它来和 0 比较(这真是无趣的细节)。编程语言的部分威力就是:它们可以为我们处理无趣的细节。

程序的末尾,在while结构完成之后,console.log操作用来打印出结果。

最后,如果我们碰巧有方便的rangesum操作可用(range用来创建一定范围内的一组数字,sum用来计算一组数字的和),那么这个程序可以写成这样:

  1. console.log(sum(range(1, 10)));
  2. // → 55

这个故事告诉我们:相同的程序可以用或长或短、不可读或可读的方式来表示。程序的第一版极其难懂,而这最后一个基本上就是大白话:把从 1 到 10 这组数字的和打印出来。(我们会在后面的章节看看如何定义类似于sumrange这样的操作。)

一门好的编程语言允许程序员在更高的层级上讨论计算机的操作,这很有用。它可以省去细节,提供方便的组块(比如whileconsole.log),允许定义自己的组块(比如sumrange),并使得这些组块能方便地组合。

JavaScript是什么?

JavaScript在1995年被引进,用来在网景(Netscape)浏览器中给网页添加程序。自此,这门语言被所有其他主流图形化 Web 浏览器采用。它使得现代 Web 应用成为可能:可直接交互而不用对每一个操作都重新加载页面。JavaScript也被用在更多的传统网站,以提供多种形式的交互和灵活性。

特别值得注意的是,JavaScript与编程语言Java几乎没有关系。相似的名称,更多的是出于市场推广的考虑。当JavaScript被引进时,Java语言正在被强烈推广,越来越受欢迎。有人认为赶上这一波成功的趋势会是不错的主意。现在我们就沿用了那个名字。

在应用于网景之外的场景之后,一个描述JavaScript语言工作方式的标准文档被制订出来,以使得那些声称支持JavaScript的各种关键实际上说的是同一门语言。这就是ECMAScript标准,诞生于ECMA国际化组织进行标准化之后。实际上,属于ECMAScriptJavaScript可以换着使用:它们是同一种语言的两个名字。

有人会说关于JavaScript的糟糕的事情。其中一些确实存在。当我第一次需要用JavaScript写点东西的时候,我很快就开始“鄙视”它了。它会接受我敲的任何内容,但是解析这些内容的方式却完成跟我的意图不同。当然,这很大程度上跟我并不十分清楚我在做些什么,但是有一个问题真实存在:JavaScript在它所允许的内容方面异常宽松。这背后的设计思想是:它使得新手用JavaScript编程更加容易。真实情况是,它通常使得查找程序中的问题更难,因为系统不会给你指出来。

但是这个灵活性也有其好处。它使得很多技术成为可能,而这些在更加严格的语言中是不可能实现的,比如:你将会看到(第10章),它可以用来克服JavaScript的一些缺点。在充分学习这门语言并用它工作了一段时间之后,我意识到真正喜欢上了JavaScript

有很多版本的JavaScriptECMAScript第 3 版在JavaScript逐渐占据主导地位的过程(大概从 2000 年到 2010 年)中被广泛支持。在此期间,关于“极具野心”的第 4 版的工作在悄然进行,这一版计划对这门语言进行许多激进的改进和扩展。事实证明,用如此激进的方式来修改一门生机勃勃、被广泛使用的语言是非常困难的,第 4 版的工作在 2008 年被叫停,自此开启了不那么激进的第 5 版(只进行了一些没有争议的改进),并于 2009 年发布。后来,在 2015 年发布了第 6 版,这是曾经计划在第 4 版中实现的一次主要更新。从此以后,每年都会有新的、小的更新。

语言一直在进化,意味着各个浏览器也得持续跟进,如果使用旧版浏览器,就可能不支持所有的语言特性。语言设计者必须小心谨慎,不能因为任何更改而导致现存的程序无法运行,如此一来,新的浏览器仍然可以运行旧版的程序。在本书中,我用的是JavaScript 2017 版。

Web 浏览器并不是JavaScript可运行的唯一平台。一些数据库,比如MongoDBCouchDB,使用JavaScript作为它们的脚本和查询语言。几个桌面端和服务器端编程平台,尤其是Node.js项目(第20章的主题),提供了在浏览器之外进行JavaScript编程的环境。

代码,以及用它做什么

代码 是组成程序的文字。本书大多数章节都包含大量的代码。我相信读、写代码是学习编程不可或缺的部分。不要只是随便看看例子,而要仔细阅读并理解。一开始这么做可能会比较慢,也比较困惑,但我保证你很快就能熟悉了。对于练习,也是一样的。不要假定你理解了它们,除非你真正写出了一个可用的解法。

我建议你在一个真实的JavaScript解析器中尝试解答这些练习。这样的话,你的答案是否正确会立即反馈出来,并且我希望能引导你超越练习去进一步实验。

运行和试验本书中示例代码最简单的方式就是在本书的在线版本 eloquentjavascript.net 中查找。

在那里,你可以点击任意代码示例来编辑、运行,查看所产生的输出。如果要做习题,可以访问 eloquentjavascript.net/code,这里提供了每个编程习题的初始代码,你也可以查看答案。

如果你想在本书网站之外的环境中运行书中的程序,需要特别注意。一些例子是独立的,可以在任何JavaScript环境中运行。但是后续一些章节的代码往往针对特定环境(浏览器或Node.js)而写,这些代码就只能在相应环境中才能运行。另外,很多章节定义了更大的程序,其中的代码需要依赖彼此或外部文件。网站上的沙盒提供了Zip包的链接,其中含有运行指定章节代码所需的所有脚本和数据文件。

本书概览

本书大概包括三部分。前 12 章讨论JavaScript语言本身。接下来 7 章是关于 Web 浏览器以及用JavaScript进行编程的内容。最后 2 章讲述另一个JavaScript的编程环境:Node.js

整本书中共有 5 个 项目章节,描述了更大的示例程序,以便让你感受真正的编程。按照出现的顺序,我们会创建一个机器人,一门编程语言,一个平台游戏,一个绘图程序和一个动态网站

本书的语言部分以介绍JavaScript语言基本结构的 4 个章节开头。它们介绍了控制结构(比如在本章之前所看到的while关键字),函数(编写自己的组块),以及数据结构。然后,你将能写基本的程序了。接着,第五章第六章介绍了使用函数和对象来编写更加 抽象 的代码的技术,以控制复杂度。

第一个项目章节之后,本书的语言部分分别介绍了错误处理和Bug修复正则表达式(处理文本的重要工具),模块化(另一个控制复杂度的技术),以及异步编程(处理耗时的事件)。第二个项目章节总结了本书第一部分。

第二部分(从第 13 章第 19 章)描述了浏览器端JavaScript可以处理的内容。你将学会在屏幕上展示事物(第 14 章和第 17 章),响应用户输入(第 15 章),以及通过网络完成通信(第 18 章)。这一部分又有 2 个项目章节。

最后,第 20 章讲述了Node.js第 21 章则用它创建了一个小网站。

排版约定

本书中,等宽字体的文字代表着程序的元素:有时候它们是独立的代码片段,有时候它们仅仅是临近程序的一部分。程序(你已经见过好几个了)被书写成这样:

  1. function factorial(n) {
  2. if (n == 0) {
  3. return 1;
  4. } else {
  5. return factorial(n - 1) * n;
  6. }
  7. }

有时候,为了显示程序生成的结果,将在其下方打印出结果,并以双斜线和一个箭头开头。

  1. console.log(factorial(8));
  2. // → 40320

祝你好运!