第 3 版已发布。点击此处阅读

简介

这是一本关于如何让计算机按照你的意愿工作的书籍。如今,计算机已经像螺丝刀一样普遍,但它们包含了更多的隐藏复杂性,因此更难操作和理解。对于许多人来说,它们仍然是陌生的,有点威胁性的东西。

Communicating with a computer

我们找到了两种有效的方法来弥合我们,这些柔软的生物体擅长社交和空间推理,以及计算机,这些无情的毫无意义数据操纵者之间的沟通差距。第一种是利用我们对物理世界的感知,构建模拟现实世界的界面,并允许我们用手指在屏幕上操纵形状。这对于非正式的机器交互非常有效。

但我们还没有找到一种好的方法来使用点击式方法向计算机传达界面设计者没有预料到的信息。对于开放式界面,例如指示计算机执行任意任务,我们使用了一种利用我们语言天赋的方法获得了更大的成功:教机器一门语言。

人类语言允许以多种方式组合词语和短语,这使我们能够说出许多不同的东西。计算机语言虽然通常语法灵活度较低,但也遵循类似的原则。

在过去的 20 年里,非正式计算已经变得更加普遍,而基于语言的界面,曾经是人们与计算机交互的默认方式,在很大程度上已被图形界面取代。但它们仍然存在,如果你知道在哪里寻找。其中一门语言,JavaScript,内置于几乎所有网络浏览器中,因此几乎在所有消费设备上都可以使用。

这本书旨在让你对这门语言足够熟悉,能够让计算机按照你的意愿工作。

关于编程

我不启发那些不愿学习的人,也不唤醒那些不愿自己解释的人。如果我展示了正方形的一个角,而他们不能用另外三个角回到我这里,我就不应该再讲一遍要点。

孔子

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

有时阅读这本书会让人感觉非常沮丧。如果你不熟悉编程,会有很多新内容需要消化。这些内容中的大部分将在需要你建立额外联系的方式进行组合

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

计算机程序员是宇宙的创造者,他[sic]对其负全部责任。实际上无限复杂的宇宙可以以计算机程序的形式创建。

约瑟夫·魏泽鲍姆,计算机能力与人类理性

一个程序是很多东西。它是程序员输入的一段文本,它是让计算机执行操作的驱动力,它是计算机内存中的数据,但它控制着对同一内存执行的操作。试图将程序与我们熟悉的物体进行比较的类比往往会失败。一个表面上合适的类比是机器——很多独立的部件都参与其中,为了让整个机器运转,我们必须考虑这些部件相互连接的方式以及它们对整体运行的贡献。

计算机是一台机器,被设计为这些非物质机器的宿主。计算机本身只能做愚蠢的简单的事情。它们之所以如此有用,是因为它们以惊人的速度做这些事情。一个程序可以巧妙地组合大量的简单操作,以完成非常复杂的事情。

对我们中的一些人来说,编写计算机程序是一场迷人的游戏。一个程序是思想的建筑。它建造起来没有成本,没有重量,并且在我们打字的手下很容易增长。

但是,如果没有小心,程序的大小和复杂性将失控,甚至连创建它的人也会感到困惑。控制程序是编程的主要问题。当一个程序工作时,它是美丽的。编程的艺术是控制复杂性的技巧。优秀的程序是低调的——在复杂性中变得简单。

许多程序员认为,通过在程序中仅使用一小套经过良好理解的技术,可以最好地管理这种复杂性。他们制定了严格的规则(“最佳实践”),规定程序应具有的形式,而那些更热心的人会认为那些违反这种安全小范围的人是糟糕的程序员。

对编程丰富性的敌意——试图将其简化为一些直接且可预测的东西,对所有奇怪而美丽的程序施加禁忌!编程技术的范围是巨大的,其多样性令人着迷,而且在很大程度上仍未被探索。当然,它很危险,会将没有经验的程序员引诱到各种困惑中,但这只是意味着你应该谨慎行事,保持头脑清醒。随着你的学习,总会有新的挑战和新的领域需要探索。拒绝继续探索的程序员将停滞不前,忘记他们的快乐,对他们的工作感到厌倦。

为什么语言很重要

在最初,在计算的诞生之初,没有编程语言。程序看起来像这样

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. Store the number 0 in memory location 0.
2. Store the number 1 in memory location 1.
3. Store the value of memory location 1 in memory location 2.
4. Subtract the number 11 from the value in memory location 2.
5. If the value in memory location 2 is the number 0,
   continue with instruction 9.
6. Add the value of memory location 1 to memory location 0.
7. Add the number 1 to the value of memory location 1.
8. Continue with instruction 3.
9. Output the value of memory location 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 中的相同程序

var 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 的数字rangesum。(我们将在后面的章节中看到如何构建像 sumrange 这样的操作。)

一门好的编程语言通过允许程序员在更高层次上谈论计算机必须执行的操作来帮助程序员。它有助于省略没有意义的细节,提供便捷的构建块(例如 whileconsole.log),允许你定义自己的构建块(例如 sumrange),并使这些块易于组合。

什么是 JavaScript?

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

重要的是要注意,JavaScript 与名为 Java 的编程语言几乎没有关系。相似的名称是受营销考虑而非良好的判断力启发的。当 JavaScript 被推出时,Java 语言正在被大力推广并越来越受欢迎。有人认为,尝试借此成功之势是个好主意。现在我们被这个名字困住了。

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

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

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

JavaScript 有多个版本。ECMAScript 版本 3 是在 JavaScript 崛起至统治地位的时期内广泛支持的版本,大约介于 2000 年至 2010 年之间。在此期间,人们正在着手进行一项雄心勃勃的版本 4 工作,该版本计划对语言进行一些根本性的改进和扩展。以如此激进的方式改变一种正在使用的语言,在政治上证明很困难,版本 4 的工作于 2008 年被放弃,导致雄心远小于它的版本 5 于 2009 年发布。现在,所有主要浏览器都支持版本 5,本书将重点介绍该语言版本。版本 6 正在最终确定中,一些浏览器开始支持此版本的新功能。

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

代码及其用途

代码是构成程序的文本。本书中的大多数章节都包含很多代码。根据我的经验,阅读代码和编写代码是学习编程不可或缺的部分,因此请不要只是浏览示例。仔细阅读并理解它们。这可能一开始很慢且令人困惑,但我保证您会很快上手。练习也是如此。不要认为您理解了它们,直到您真正编写了一个有效的解决方案。

我建议您尝试在实际的 JavaScript 解释器中解决练习。这样,您将立即获得关于您的操作是否有效的反馈,并且我希望,您会想尝试和超越练习。

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

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

本书概述

本书大致分为三个部分。前 11 章讨论 JavaScript 语言本身。接下来的八章是关于 Web 浏览器以及 JavaScript 如何用于对其进行编程的。最后,两章专门介绍 Node.js,另一个用于编程 JavaScript 的环境。

全书共有五个项目章节,描述了更大的示例程序,让您体验真实的编程。我们将按顺序完成构建一个 人工生命模拟、一个 编程语言、一个 平台游戏、一个 绘图程序 和一个 动态网站

本书的语言部分从四章开始,介绍 JavaScript 语言的基本结构。它们介绍了 控制结构(例如您在本介绍中看到的 while 字)、函数(编写您自己的操作)和 数据结构。掌握这些之后,您就可以编写简单的程序。接下来,第 5 章第 6 章 介绍了使用函数和对象来编写更抽象的代码,从而控制复杂性的技术。

第一个项目章节 之后,本书的第一部分继续介绍关于 错误处理和修复、关于 正则表达式(处理文本数据的重要的工具)以及关于 模块化——对抗复杂性的另一种武器。第二个项目章节 结束了本书的第一部分。

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

之后,第 20 章 描述了 Node.js,第 21 章 使用该工具构建了一个简单的 Web 系统。

排版约定

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

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

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

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

祝你好运!