第四版现已推出。点击这里阅读

简介

我们认为,我们正在为自己的目的创建系统。我们相信我们正在以自己的形象创造它……但计算机并不真正像我们。它是我们自身极小一部分的投射:致力于逻辑、秩序、规则和清晰的那一部分。

艾伦·乌尔曼,《靠近机器:技术狂热及其不满》
Picture of a screwdriver and a circuit board

这是一本关于指示计算机的书籍。如今,计算机与螺丝刀一样普遍,但它们要复杂得多,让它们按你的意愿行事并不总是容易的。

如果你对计算机的任务是常见且易于理解的任务,例如查看你的电子邮件或充当计算器,你可以打开相应的应用程序并开始工作。但对于独特或开放式任务,可能没有应用程序。

这就是编程可能发挥作用的地方。编程是构建程序的行为——一组精确的指令,告诉计算机该做什么。因为计算机是愚蠢的、墨守成规的野兽,所以编程从根本上来说是乏味和令人沮丧的。

幸运的是,如果你能克服这个事实,也许还能享受用愚蠢的机器可以理解的方式思考的严谨性,编程会很有回报。它允许你在几秒钟内完成手动操作需要永远才能完成的事情。它是一种让你的计算机工具做以前无法做的事情的方法。它还提供了一种奇妙的抽象思维练习。

大多数编程都是用编程语言完成的。编程语言是一种人工构建的语言,用于指示计算机。有趣的是,我们发现与计算机沟通最有效的方式,很大程度上借鉴了我们彼此沟通的方式。与人类语言一样,计算机语言允许将单词和短语以新的方式组合起来,从而有可能表达不断涌现的新概念。

在某个时候,基于语言的界面(例如 1980 年代和 1990 年代的 BASIC 和 DOS 提示符)是与计算机交互的主要方法。它们在很大程度上已被视觉界面取代,视觉界面更容易学习,但提供了更少的自由度。如果你知道在哪里找,计算机语言仍然存在。其中一种语言,JavaScript,内置于每个现代 Web 浏览器中,因此几乎可以在每台设备上使用。

本书将努力让你足够熟悉这种语言,以便用它做一些有用且有趣的事情。

关于编程

除了解释 JavaScript,我还会介绍编程的基本原理。事实证明,编程很难。基本规则简单明了,但在这些规则之上构建的程序往往变得足够复杂,从而引入自己的规则和复杂性。在某种程度上,你正在构建自己的迷宫,你可能会迷失其中。

有时阅读本书会让人感到非常沮丧。如果你不熟悉编程,将会有很多新的内容需要消化。这些内容中的许多将以结合的方式结合起来,要求你建立额外的联系。

你需要付出必要的努力。当你难以理解本书时,不要对自己的能力妄下结论。你很好——你只需要坚持下去。休息一下,重新阅读一些资料,并确保阅读和理解示例程序和练习。学习是苦差事,但你学到的所有东西都是你的,并将使之后的学习更容易。

当行动变得无利可图时,收集信息;当信息变得无利可图时,睡觉。

厄休拉·K·勒古恩,《黑暗的左手》

程序是许多事物。它是程序员键入的一段文本,它是使计算机执行其操作的引导力量,它是计算机内存中的数据,但它控制对同一内存执行的操作。试图将程序与我们熟悉的物体进行比较的类比往往会失败。一个表面上合适的类比是机器——往往会涉及许多独立的部分,为了使整个机器运转起来,我们必须考虑这些部分相互连接的方式以及它们对整体运行的贡献。

计算机是一台物理机器,它充当这些非物质机器的宿主。计算机本身只能做愚蠢的、简单的操作。它们如此有用的原因是它们以令人难以置信的速度执行这些操作。一个程序可以巧妙地将大量这些简单的动作组合起来,完成非常复杂的事情。

程序是思想的构建。它没有建造成本,没有重量,并且在我们的打字手中很容易生长。

但如果不加注意,程序的大小和复杂性将失控,甚至会让创建它的人感到困惑。控制程序是编程的主要问题。当一个程序工作时,它很美。编程的艺术是控制复杂性的技能。伟大的程序是收敛的——在复杂性中变得简单。

一些程序员认为,这种复杂性最好通过在程序中仅使用一组经过充分理解的技术来管理。他们制定了严格的规则(“最佳实践”),规定程序应该具有的形式,并小心地停留在他们的安全区域。

这不仅乏味,而且无效。新问题往往需要新的解决方案。编程领域还很年轻,并且还在快速发展,它足够多样化,可以容纳各种不同的方法。在程序设计中,有很多错误可以犯,你应该继续犯这些错误,以便你理解它们。对良好程序外观的理解是通过实践得来的,而不是从规则列表中学习的。

为什么语言很重要

在开始的时候,在计算诞生的初期,没有编程语言。程序看起来像这样

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。它可以在一台简单的假设机器上运行。要对早期计算机进行编程,需要将大量开关设置在正确的位置,或者在纸带上打孔,然后将纸带输入计算机。你可以想象这个过程是多么的乏味和容易出错。即使编写简单的程序也需要极大的聪明才智和纪律。复杂的程序几乎无法想象。

当然,手动输入这些神秘的位模式(0 和 1)确实让程序员有一种成为强大巫师的感觉。在工作满意度方面,这必然是值得的。

前一个程序的每一行都包含一个指令。它可以用英文写成这样

  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,以确定它是否可以停止运行。因为我们的假设机器非常原始,它只能测试一个数字是否为零并根据该值做出决定。因此,它使用标记为 compare 的内存位置计算 count - 11 的值,并根据该值做出决定。接下来的两行每次程序确定 count 尚未达到 11 时,就会将 count 的值添加到结果中,并将 count 递增 1。

以下是 JavaScript 中的同一程序

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

这个版本给我们带来了一些改进。最重要的是,我们不再需要指定程序如何来回跳转。while 结构会处理这个问题。只要给定的条件成立,它就会继续执行它下面的代码块(用大括号括起来)。这个条件是 count <= 10,意思是“count 小于或等于 10”。我们不再需要创建一个临时值并将其与零比较,这只是一个无关紧要的细节。编程语言的力量之一是它们可以帮我们处理这些无关紧要的细节。

在程序结束时,while 结构执行完后,console.log 操作用于输出结果。

最后,如果我们碰巧可以使用 rangesum 运算符,程序可能会看起来像这样,这两个运算符分别创建一个范围内数字的集合并计算数字集合的总和。

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

这个故事的寓意是,同一个程序可以用长短不一、可读性不同的方式表达。程序的第一个版本非常晦涩,而最后一个版本几乎就是英语:log 从 1 到 10 的数字的sum。(我们将在后面的章节中看到如何定义像 sumrange 这样的操作。)

一个好的编程语言可以通过让程序员用更高级的语言描述计算机需要执行的操作来帮助程序员。它有助于省略细节,提供方便的构建块(如 whileconsole.log),允许您定义自己的构建块(如 sumrange),并使这些块易于组合。

什么是 JavaScript?

JavaScript 于 1995 年被引入,用于在 Netscape Navigator 浏览器中为网页添加程序。该语言此后已被所有其他主要的图形 Web 浏览器采用。它使现代 Web 应用程序成为可能——您可以直接与之交互的应用程序,无需为每个操作重新加载页面。JavaScript 也用于更传统的网站,以提供各种形式的交互性和灵活性。

需要注意的是,JavaScript 与名为 Java 的编程语言几乎没有关系。相似的名称是出于营销考虑而不是良好的判断。当 JavaScript 被引入时,Java 语言正在被大力推广并日益流行。有人认为,尝试借此成功是一个好主意。现在我们被这个名字困住了。

在 Netscape 之外被采用后,编写了一份标准文档来描述 JavaScript 语言的工作方式,以便声称支持 JavaScript 的各种软件实际上讨论的是同一种语言。这被称为 ECMAScript 标准,以进行标准化的 Ecma 国际组织命名。实际上,ECMAScript 和 JavaScript 这些术语可以互换使用——它们是同一种语言的两个名称。

有些人会对 JavaScript 说出可怕的话。其中许多都是真的。当我第一次被要求用 JavaScript 编写一些东西时,我很快就开始讨厌它。它几乎可以接受我输入的任何内容,但它以一种与我的本意完全不同的方式解释它。当然,这与我对我在做什么一无所知有关,但这里确实存在一个问题:JavaScript 对允许的内容非常宽松。这种设计的理念是,它将使初学者更容易用 JavaScript 进行编程。实际上,它主要是让在程序中找到问题变得更加困难,因为系统不会向你指出它们。

不过,这种灵活性也有它的优点。它为在更严格的语言中不可能实现的许多技术留下了空间,正如您将看到的那样(例如在第 10 章中),它可以用来克服 JavaScript 的一些缺点。在正确学习并使用该语言一段时间后,我学会了真正喜欢 JavaScript。

JavaScript 已经发布了几个版本。在 JavaScript 崛起为统治地位的时代,大约在 2000 年到 2010 年之间,ECMAScript 版本 3 是被广泛支持的版本。在此期间,人们一直在努力开发一个雄心勃勃的版本 4,该版本计划对该语言进行一些根本性的改进和扩展。以这样一种激进的方式改变一种活跃的、被广泛使用的语言被证明在政治上很困难,版本 4 的工作在 2008 年被放弃,导致了一个远不那么雄心勃勃的版本 5,该版本只做了一些无争议的改进,于 2009 年发布。然后在 2015 年,版本 6 发布了,这是一个重大更新,其中包括为版本 4 计划的一些想法。从那时起,我们每年都会发布新的、小的更新。

该语言不断发展的事实意味着浏览器必须不断跟上,如果您使用的是旧版浏览器,它可能不支持所有功能。语言设计者非常小心,不会进行任何可能破坏现有程序的更改,因此新浏览器仍然可以运行旧程序。在这本书中,我使用的是 2017 年版的 JavaScript。

Web 浏览器并不是使用 JavaScript 的唯一平台。一些数据库,如 MongoDB 和 CouchDB,使用 JavaScript 作为它们的脚本和查询语言。几个用于桌面和服务器编程的平台,最值得注意的是 Node.js 项目(第 20 章 的主题),提供了在浏览器之外编程 JavaScript 的环境。

代码以及如何使用它

代码是构成程序的文本。本书中的大多数章节都包含大量的代码。我相信阅读代码和编写代码是学习编程不可或缺的一部分。不要只是浏览示例——认真阅读并理解它们。这在一开始可能会很慢且令人困惑,但我保证你很快就会掌握它。练习也是如此。在你真正写出有效的解决方案之前,不要认为你理解它们。

我建议你在一个真实的 JavaScript 解释器中尝试你的练习解决方案。这样,你就可以立即得到关于你的操作是否有效的反馈,我希望,你会被诱惑去尝试并超越练习。

在浏览器中阅读本书时,您可以通过单击所有示例程序来编辑(并运行)它们。

如果您想在本书网站之外运行本书中定义的程序,则需要谨慎处理。许多示例都是独立的,应该在任何 JavaScript 环境中都能运行。但是,后面章节中的代码通常是为特定环境(浏览器或 Node.js)编写的,只能在那里运行。此外,许多章节定义了更大的程序,其中出现的代码片段相互依赖或依赖于外部文件。网站上的沙箱提供了指向 Zip 文件的链接,这些文件包含运行给定章节代码所需的所有脚本和数据文件。

本书概述

本书包含大约三个部分。前 12 章讨论 JavaScript 语言。接下来的七章是关于 Web 浏览器以及使用 JavaScript 为它们编程的方式。最后,两章专门介绍 Node.js,另一个用于编程 JavaScript 的环境。

在整本书中,有五个项目章节,这些章节描述了更大的示例程序,让您体验实际的编程。按照出现的顺序,我们将逐步构建一个送货机器人、一个编程语言、一个平台游戏、一个像素绘画程序和一个动态网站.

本书的语言部分从四章开始,介绍 JavaScript 语言的基本结构。它们介绍了控制结构(如您在本引言中看到的 while 词)、函数(编写您自己的构建块)和数据结构。学习完这些之后,您将能够编写基本的程序。接下来,第5 章和第6 章介绍使用函数和对象编写更抽象代码并控制复杂性的技术。

经过第一个项目章节之后,本书的语言部分继续介绍错误处理和调试正则表达式(处理文本的重要工具)、模块化(另一种防御复杂性的方法)和异步编程(处理需要时间的事件)。第二个项目章节总结了本书的第一部分。

第二部分,第13 章到第19 章,描述了浏览器 JavaScript 可以访问的工具。您将学习在屏幕上显示内容(第14 章和第17 章)、响应用户输入(第 15 章)以及通过网络进行通信(第 18 章)。这一部分中还有两个项目章节。

之后,第 20 章描述了 Node.js,而第 21 章使用该工具构建了一个小型网站。

排版约定

在这本书中,用等宽字体书写的文本表示程序的元素——有时它们是自包含的片段,有时它们只是指附近程序的一部分。程序(您已经见过几个了)的书写方式如下

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

有时,为了显示程序产生的输出,期望的输出将写在它后面,前面有两个斜杠和一个箭头。

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

祝你好运!