# 零、前言

> 原文：[Introduction](https://eloquentjavascript.net/00_intro.html)
> 
> 译者：[飞龙](https://github.com/wizardforcel)
> 
> 协议：[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
> 
> 自豪地采用[谷歌翻译](https://translate.google.cn/)
> 
> 部分参考了[《JavaScript 编程精解（第 2 版）》](https://book.douban.com/subject/26707144/)

> We think we are creating the system for our own purposes. We believe we are making it in our own image... But the computer is not really like us. It is a projection of a very slim part of ourselves: that portion devoted to logic, order, rule, and clarity.

> Ellen Ullman，《Close to the Machine: Technophilia and its Discontents》

![](img/0-0.jpg)

这是一本关于指导电脑的书。时至今日，计算机就像螺丝刀一样随处可见，但相比于螺丝刀而言，计算机更复杂一些，并且，让他们做你想让他们做的事情，并不总是那么容易。

如果你让计算机执行的任务是常见的，易于理解的任务，例如向你显示你的电子邮件，或像计算器一样工作，则可以打开相应的应用并开始工作。 但对于独特的或开放式的任务，应用可能不存在。


这就是编程可能出现的地方。编程是构建一个程序的行为 - 它是一组精确的指令，告诉计算机做什么。 由于计算机是愚蠢的，迂腐的野兽，编程从根本上是乏味和令人沮丧的。

幸运的是，如果你可以克服这个事实，并且甚至可以享受愚蠢机器可以处理的严谨思维，那么编程可以是非常有益的。 它可以让你在几秒钟内完成手动操作。 这是一种方法，让你的电脑工具去做它以前做不到的事情。 它也提供了抽象思维的优秀练习。

大多数编程都是用编程语言完成的。 编程语言是一种人工构建的语言，用于指导计算机。 有趣的是，我们发现与电脑沟通的最有效的方式，与我们彼此沟通的方式相差太大。 与人类语言一样，计算机语言可以以新的方式组合词语和词组，从而可以表达新的概念。

在某种程度上，基于语言的界面，例如 80 年代和 90 年代的 BASIC 和 DOS 提示符，是与计算机交互的主要方法。 这些已经在很大程度上被视觉界面取代，这些视觉界面更容易学习，但提供更少的自由。 计算机语言仍然存在，如果你知道在哪里看到。 每种现代 Web 浏览器都内置了一种这样的语言，即 JavaScript，因此几乎可以在所有设备上使用。

本书将试图让你足够了解这门语言，从而完成有用和有趣的东西。

## 关于程序设计

除了讲解 JavaScript 之外，本书也会介绍一些程序设计的基本原则。程序设计还是比较复杂的。编程的基本规则简单清晰，但在这些基本规则之上构建的程序却容易变得复杂，导致程序产生了自己的规则和复杂性。即便程序是按照你自己的思路去构建的，你也有可能迷失在代码之间。

在阅读本书时，你有可能会觉得书中的概念难以理解。如果你刚刚开始学习编程，那么你估计还有不少东西需要掌握呢。如果你想将所学知识融会贯通，那么就需要去多参考和学习一些资料。

是否付出必要的努力完全取决于你自己。当你阅读本书的时候发现任何难点，千万不要轻易就对自己的能力下结论。只要能坚持下去，你就是好样的。稍做休息，复习一下所学的知识点，始终确保自己阅读并理解了示例程序和相关的练习。学习是一项艰巨的任务，但你掌握的所有知识都属于你自己，而且今后的学习道路会愈加轻松。

> 当行动无利可图时，就收集信息；当信息无利可图时，就休息。

> Ursula K. Le Guin，《The Left Hand of Darkness》

一个程序有很多含义：它是开发人员编写的一段文本、计算机执行的一段指令集合、计算机内存当中的数据以及控制内存中数据的操作集合。我们通常很难将程序与我们日常生活中熟悉的事物进行对比。有一种表面上比较恰当的比喻，即将程序视作包含许多组件的机器，为了让机器正常工作，这些组件通过内部通信来实现整个机器的正常运转。

计算机是一台物理机器，充当这些非物质机器的载体。计算机本身并不能实现多么复杂的功能，但计算机之所以有用是因为它们的运算速度非常快。而程序的作用就是将这些看似简单的动作组合起来，然后实现复杂的功能。

程序是思想的结晶。编写程序不需要什么物质投入，它很轻量级，通过我们的双手创造。

但如果不稍加注意，程序的体积和复杂度就会失去控制，甚至代码的编写者也会感到迷惑。在可控的范围内编写程序是编程过程中首要解决的问题。当程序运行时，一切都是那么美好。编程的精粹就在于如何更好地控制复杂度。质量高的程序的复杂度都不会太高。

很多开发人员认为，控制程序复杂度的最好方法就是避免使用不熟悉的技术。他们制定了严格的规则（“最佳实践”），并小心翼翼地呆在他们安全区内。

这不仅无聊，而且也是无效的。新问题往往需要新的解决方案。编程领域还很年轻，仍然在迅速发展，并且多样到足以为各种不同的方法留出空间。在程序设计中有许多可怕的错误，你应该继续犯错，以便你能理解它们。好的程序看起来是什么样的感觉，是在实践中发展的，而不是从一系列规则中学到的。

## 为什么编程语言重要

在计算技术发展伊始，并没有编程语言这个概念。程序看起来就像这样：

```
00110001 00000000 00000000
00110001 00000001 00000001
00110011 00000001 00000010
01010001 00001011 00000010
00100010 00000010 00001000
01000011 00000001 00000000
01000001 00000001 00000001
00010000 00000010 00000000
01100010 00000000 00000000
```

该程序计算数字 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 中的值。

虽说这已经比一大堆位序列要好读了许多，但仍然不清晰。使用名称而不是数字用于指令和存储位置有所帮助：

```
 Set “total” to 0.
 Set “count” to 1.
[loop]
 Set “compare” to “count”.
 Subtract 11 from “compare”.
 If “compare” is zero, continue at [end].
 Add “count” to “total”.
 Add 1 to “count”.
 Continue at [loop].
[end]
 Output “total”.
```

现在你能看出该程序是如何工作的吗？前两行代码初始化两个内存位置的值：`total`用于保存累加计算结果，而`count`则用于记录当前数字。你可能觉得`compare`的那行代码看起来有些奇怪。程序想根据`count`是否等于 11 来决定是否应该停止运行。因为我们的机器相当原始，所以只能测试一个数字是否为 0，并根据它做出决策。因此程序用名为`compare`的内存位置存放`count–11`的值，并根据该值是否为 0 决定是否跳转。接下来两行将`count`的值累加到结果上，并将`count`加 1，直到`count`等于`11`为止。

下面使用 JavaScript 重新编写了上面的程序：

```js
let total = 0, count = 1;
while (count <= 10) {
  total += count;
  count += 1;
}
console.log(total);
// → 55
```

这个版本的程序得到了一些改进。更为重要的是，我们再也不需要指定程序如何来回跳转了，而是由`while`结构负责完成这个任务。只要我们给予的条件成立，`while`语句就会不停地执行其下方的语句块（包裹在大括号中）。而我们给予的条件是`count<=10`，意思是“`count`小于等于 10”。我们再也不需要创建临时的值并将其与 0 比较，那样的代码十分烦琐。编程语言的一项职责就是，能够帮助我们处理这些烦琐无趣的逻辑。

在程序的结尾，也就是`while`语句结束后，我们使用`console.log`操作来输出结果。

最后，我们恰好有`range`和`sum`这类方便的操作。下面代码中的`range`函数用于创建数字集合，`sum`函数用于计算数字集合之和：

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

我们可以从这里了解到，同一个程序的长度可长可短，可读性可高可低。第一个版本的程序晦涩难懂，而最后一个版本的程序则接近于人类语言的表达方式：将 1~10 范围内的数字之和记录下来（我们会在后面的章节中详细介绍如何编写`sum`和`range`这样的函数）。

优秀的编程语言可以为开发人员提供更高层次的抽象，使用类似于人类语言的方式来与计算机进行交互。它有助于省略细节，提供便捷的积木（比如`while`和`console.log`），允许你定义自己的积木（比如`sum`和`range`函数），并使这些积木易于编写。。

## 什么是 JavaScript

JavaScript 诞生于 1995 年。起初，Netscape Navigator 浏览器将其运用在网页上添加程序。自此以后，各类主流图形网页浏览器均采用了 JavaScript。JavaScript 使得现代网页应用程序成为可能 —— 使用 JavaScript 可以直接与用户交互，从而避免每一个动作都需要重新载入页面。但有许多传统网站也会使用 JavaScript 来提供实时交互以及更加智能的表单功能。

JavaScript 其实和名为Java的程序设计语言没有任何关系。起了这么一个相似的名字完全是市场考虑使然，这并非是一个明智的决定。当 JavaScript 出现时，Java 语言已在市场上得到大力推广且拥有了极高人气，因此某些人觉得依附于 Java 的成功是个不错的主意。而我们现在已经无法摆脱这个名字了。

在 JavaScript 被广泛采用之后，ECMA 国际制订了一份标准文档来描述 JavaScript 的工作行为，以便所有声称支持 JavaScript 的软件都使用同一种语言。标准化完成后，该标准被称为 ECMAScript 标准。实际上，术语 ECMAScript 和 JavaScript 可以交换使用。它们不过是同一种语言的两个名字而已。

许多人会说 JavaScript 语言的坏话。这其中有很多这样的言论都是正确的。当被要求第一次使用 JavaScript 编写代码时，我当时就觉得这门语言难以驾驭。JavaScript 接受我输入的任何代码，但是又使用和我的想法完全不同的方式来解释代码。由于我没有任何线索知道我之前做了什么，因此我需要做出更多工作，但这也就存在一个实际问题：我们可以自由使用 JavaScript，而这种自由却几乎没有限度。这种设计其实是希望初学者更容易使用 JavaScript 编写程序。但实际上，系统不会指出我们错在何处，因此从程序中找出问题变得更加棘手。

但这种自由性也有其优势，许多技术在更为严格的语言中不可能实现，而在 JavaScript 中则留下了实现的余地，正如你看到的那样（比如第十章），有些优势可以弥补 JavaScript 的一些缺点。在正确地学习 JavaScript 并使用它工作了一段时间后，我真正喜欢上了 JavaScript。

JavaScript 版本众多。大约在 2000~2010 年间，这正是 JavaScript 飞速发展的时期，浏览器支持最多的是 ECMAScript 3。在此期间，ECMA 着手制定 ECMAScript 4，这是一个雄心勃勃的版本，ECMA 计划在这个版本中加入许多彻底的改进与扩展。但由于 ECMAScript 3 被广泛使用，这种过于激进的修改必然会遭遇重重阻碍，最后 ECMA 不得不于 2008 年放弃了版本 4 的制定。这就产生了不那么雄心勃勃的版本 5，这只是一些没有争议的改进，出现在 2009 年。 然后版本 6 在 2015 年诞生，这是一个重大的更新，其中包括计划用于版本 4 的一些想法。从那以后，每年都会有新的更新。

语言不断发展的事实意味着，浏览器必须不断跟上，如果你使用的是较老的浏览器，它可能不支持每个特性。 语言设计师会注意，不要做任何可能破坏现有程序的改变，所以新的浏览器仍然可以运行旧的程序。 在本书中，我使用的是 2017 版的 JavaScript。

Web 浏览器并不是唯一一个可以运行 JavaScript 的平台。有些数据库，比如 MongoDB 和 CouchDB，也使用 JavaScript 作为脚本语言和查询语言。一些桌面和服务器开发的平台，特别是 Node.js 项目（第二十章介绍），为浏览器以外的 JavaScript 编程提供了一个环境。

## 代码及相关工作

代码是程序的文本内容。本书多数章节都介绍了大量代码。我相信阅读代码和编写代码是学习编程不可或缺的部分。尝试不要仅仅看一眼示例，而应该认真阅读并理解每个示例。刚开始使用这种方式可能会速度较慢并为代码所困惑，但我坚信你很快就可以熟能生巧。对待习题的方法也应该一样。除非你确实已经编写代码解决了问题，否则不要假设你已经理解了问题。

建议读者应尝试在实际的 JavaScript 解释器中执行习题代码。这样一来，你就可以马上获知代码工作情况的反馈，而且我希望读者去做更多的试验，而不仅仅局限于习题的要求。

可以在 <http://eloquentjavascript.net/> 中查阅本书的在线版本，并运行和实验本书中的代码。也可以在在线版本中点击任何代码示例来编辑、运行并查看其产生的输出。在做习题时，你可以访问 <http://eloquentjavascript.net/code/>，该网址会提供每个习题的初始代码，让你专心于解答习题。

如果想要在本书提供的沙箱以外执行本书代码，需要稍加注意。许多的示例是独立的，而且可以在任何 JavaScript 环境下运行。但后续章节的代码大多数都是为特定环境（浏览器或者 Node.js）编写的，而且只能在这些特定环境下执行代码。此外，许多章节定义了更大的程序，这些章节中出现的代码片段会互相依赖或是依赖于一些外部文件。本书网站的沙箱提供了 zip 压缩文件的链接，该文件包含了所有运行特定章节代码所需的脚本和数据文件。

## 本书概览

本书包括三个部分。前十二章讨论 JavaScript 语言本身的一些特性。接下来的 8 章讨论网页浏览器和 JavaScript 在网页编程中的实践。最后两章专门讲解另一个使用 JavaScript 编程的环境 —— Node.js。

纵观本书，共有 5 个项目实战章，用于讲解规模较大的示例程序，你可以通过这些章来仔细品味真实的编程过程。根据项目出现次序，我们会陆续构建递送机器人（7）、程序设计语言（12）、平台游戏（16）、像素绘图程序（19）和一个动态网站（21）。

本书介绍编程语言时，首先使用4章来介绍 JavaScript 语言的基本结构，包括第二章控制结构（比如在本前言中看到的`while`单词）、第三章函数（编写你自己的积木）和第四章数据结构。此后你就可以编写简单的程序了。接下来，第五章和第六章介绍函数和对象的运用技术，以编写更加抽象的代码并以此来控制复杂度。

介绍完第一个项目实战（7）之后，将会继续讲解语言部分，例如第八章错误处理和 bug 修复、第九章正则表达式（处理文本数据的重要工具）、第十章模块化（解决复杂度的问题）以及第十一章异步编程（处理需要时间的事件）。第二个项目实战章节（12）则是对本书第一部分的总结。

第二部分（第十三章到第十九章），阐述了浏览器 JavaScript 中的一些工具。你将会学到在屏幕上显示某些元素的方法（第十四章与第十七章），响应用户输入的方法（第十五章）和通过网络通信的方法（第十八章）。这部分又有两个项目实战章节。

此后，第二十章阐述 Node.js，而第二十一章使用该工具构建一个简单的网页系统。

## 本书版式约定

本书中存在大量代码，程序（包括你迄今为止看到的一些示例）代码的字体如下所示：

```js
function factorial(n) {
  if (n == 0) {
    return 1;
  } else {
    return factorial(n - 1) * n;
  }
}
```

为了展示程序产生的输出，本书常在代码后编写代码期望输出，输出结果前会加上两个反斜杠和一个箭头。

```js
console.log(factorial(8));
// → 40320
```

祝好运！
