介绍
我们认为我们是为了自己的目的而创建系统。我们相信我们是根据自己的形象来构建系统的……但计算机与我们并不完全一样。它只是我们自身非常小的一部分的投影:那部分专注于逻辑、秩序、规则和清晰度。
—— 艾伦·厄尔曼,《接近机器:技术狂热及其不满》

这是一本关于指导计算机的书。如今,计算机就像螺丝刀一样常见,但它们比螺丝刀要复杂得多,让它们做你想做的事并不总是那么容易。
如果你要电脑执行的任务是常见的、易于理解的任务,例如显示电子邮件或充当计算器,那么你可以打开相应的应用程序并开始工作。但对于独特或开放式的任务,通常没有合适的应用程序。
编程就是为此而生的。编程就是编写程序的行为 —— 一组告诉计算机该做什么的精确指令。由于计算机是愚蠢、迂腐的野兽,编程从根本上来说是乏味而令人沮丧的。
幸运的是,如果你能克服这个事实 —— 甚至享受用愚蠢的机器可以处理的严谨思维 —— 编程可以带来回报。它让你在几秒钟内完成手工需要很长时间才能完成的事情。这是一种让你的计算机工具做以前无法做到的事情的方法。最重要的是,它是一种解决谜题和抽象思维的精彩游戏。
大多数编程都是用编程语言完成的。编程语言是一种人工构建的语言,用于指导计算机。有趣的是,我们发现与计算机通信的最有效方式大量借鉴了我们彼此交流的方式。与人类语言一样,计算机语言允许以新的方式组合单词和短语,从而可以表达新的概念。
曾经,基于语言的界面(例如 20 世纪 80 年代和 90 年代的 BASIC 和 DOS 提示符)是与计算机交互的主要方法。对于日常计算机使用,这些界面已基本被可视化界面所取代,后者更容易学习,但自由度较低。但是,如果你知道去哪里找,这些语言仍然存在。其中一种语言 JavaScript 内置于每个现代网络浏览器中,因此几乎可以在所有设备上使用。
本书将尝试让您足够熟悉这门语言,以便用它做一些有用和有趣的事情。
关于编程
除了解释 JavaScript,我还会介绍编程的基本原理。事实证明,编程很难。基本规则简单明了,但基于这些规则构建的程序往往会变得非常复杂,以至于引入自己的规则和复杂性。在某种程度上,你正在构建自己的迷宫,你很容易迷失其中。
有时阅读这本书会让人感到非常沮丧。如果你是编程新手,那么会有很多新材料需要消化。这些材料中的许多内容将以需要你建立额外联系的方式组合在一起。
这取决于你是否付出必要的努力。当你很难跟上本书的进度时,不要对自己的能力妄下结论。你没问题——你只需要坚持下去。休息一下,重新阅读一些材料,并确保你阅读并理解示例程序和练习。学习是艰苦的工作,但你学到的一切都属于你自己,并将使进一步的学习变得更容易。
当行动变得无利可图时,收集信息;当信息变得无利可图时,睡觉。
—— 厄休拉·勒古恩,《黑暗的左手》
程序有很多含义。它是程序员输入的一段文本,是让计算机执行其任务的指令,是计算机内存中的数据,同时,它控制着对该内存执行的操作。试图将程序与熟悉的物体进行比较的类比往往不够充分。一个表面上合适的类比是将程序比作机器——它往往涉及许多独立的部分,为了让整个机器运转,我们必须考虑这些部分如何相互连接并促进整体的运行。
计算机是一种物理机器,充当这些非物质机器的主机。计算机本身只能做一些非常简单的事情。它们如此有用的原因是它们以极高的速度完成这些事情。程序可以巧妙地组合大量这些简单的操作来做非常复杂的事情。
程序是思想的结晶。构建它不需要花费任何成本,它轻而易举,在我们的打字之手下很容易成长。但随着程序的增长,它的复杂性也会增加。编程技巧就是构建不会让程序员感到困惑的程序的技巧。最好的程序是那些能够做一些有趣的事情同时又易于理解的程序。
有些程序员认为,在程序中只使用一小部分易于理解的技术,就能最好地管理这种复杂性。他们制定了严格的规则(“最佳实践”),规定了程序应具有的形式,并小心翼翼地保持在他们的安全区内。
这不仅无聊,而且效率低下。新问题往往需要新的解决方案。编程领域很年轻,仍在快速发展,而且变化多端,有足够的空间容纳截然不同的方法。在程序设计中会犯很多可怕的错误,你应该至少犯一次,这样你才能理解它们。对一个好的程序的感觉是通过实践来培养的,而不是从一堆规则中学到的。
语言为何如此重要
计算机诞生之初,没有编程语言。程序看起来是这样的:
00110001 00000000 0000000000110001 00000001 0000000100110011 00000001 0000001001010001 00001011 0000001000100010 00000010 0000100001000011 00000001 0000000001000001 00000001 0000000100010000 00000010 0000000001100010 00000000 00000000这是一个将 1 到 10 的数字相加并打印结果的程序:。它可以在一台简单的假想机器上运行。要编写早期的计算机程序,必须将大量开关设置在正确的位置,或在纸板条上打孔并将其送入计算机。您可以想象这个过程是多么繁琐和容易出错。即使编写简单的程序也需要很大的智慧和纪律。复杂的程序几乎是不可想象的。1 + 2 + … + 10 = 55
当然,手动输入这些神秘的位模式(1 和 0)确实让程序员深刻感受到自己是一个强大的巫师。从工作满足感的角度来看,这一定值得一提。
上述程序的每一行都包含一条指令。它可以用英语写成这样:
- 将数字 0 存储在内存位置 0 中。
- 将数字 1 存储在内存位置 1 中。
- 将内存位置 1 的值存储在内存位置 2 中。
- 从内存位置 2 的值中减去数字 11。
- 如果内存位置 2 中的值是数字 0,则继续执行指令 9。
- 将内存位置 1 的值添加到内存位置 0。
- 将数字 1 添加到内存位置 1 的值中。
- 继续执行指令 3。
- 输出内存位置 0 的值。
虽然这已经比一堆位更易读了,但它仍然相当晦涩。使用名称而不是数字来表示指令和内存位置会有所帮助。
将“total”设置为 0。 将“count”设置为 1。[loop] 将“compare”设置为“计数”。 从“compare”中减去 11。 如果“compare”为 0,则从 [end] 继续。 将“count”添加到“total”。 将“count”加 1。 继续[loop]。[end] 输出“total”。此时您能看出程序是如何工作的吗?前两行给出了两个内存位置的起始值:total 将用于构建计算结果,并将 count 跟踪我们当前正在查看的数字。使用行 compare 可能是最令人困惑的。程序希望查看是否 count 等于 11 以决定是否可以停止运行。由于我们假设的机器相当原始,它只能测试数字是否为零并据此做出决定。因此,它使用标记的内存位置 compare 来计算的值 count - 11 并根据该值做出决定。接下来的两行将的值添加 count 到结果中,并且 count 每次程序确定 count 还不是 11 时就加 1。
以下是用 JavaScript 编写的相同程序:
let total = 0, count = 1;while (count <= 10) { total += count; count += 1;}console.log(total);// → 55此版本为我们提供了更多改进。最重要的是,我们不再需要指定我们希望程序来回跳转的方式——while 构造会处理这些。只要给定的条件成立,它就会继续执行下面的块(括在括号中)。该条件是 count <= 10,表示“计数小于或等于 10”。我们不再需要创建一个临时值并将其与零进行比较,这只是一个无趣的细节。编程语言的强大之处在于它们可以为我们处理无趣的细节。
在程序的最后,while 构造完成后,console.log 使用该操作写出结果。
range 最后,如果我们碰巧有方便的操作和可用,程序看起来会像这样,sum 分别创建一个范围内的数字集合并计算数字集合的总和:
console.log(sum(range(1, 10)));// → 55这个故事的寓意是,同一个程序可以用长和短、难读和易读的方式表达。该程序的第一个版本非常晦涩难懂,而最后一个版本几乎是英文的:从 1 到 10 的 log 数字。 (我们将在后面的章节中看到如何定义像 sum 和 range 这样的操作。)
良好的编程语言可以帮助程序员在更高层次上讨论计算机必须执行的操作。它有助于省略细节,提供方便的构建块(例如 while 和 console.log),允许您定义自己的构建块(例如 sum 和 range),并使这些块易于编写。
什么是 JavaScript?
JavaScript 于 1995 年推出,用于在 Netscape Navigator 浏览器中向网页添加程序。此后,所有其他主流图形 Web 浏览器都采用了该语言。它使现代 Web 应用程序成为可能,即您可以直接与之交互的应用程序,而无需为每次操作重新加载页面。JavaScript 还用于更传统的网站,以提供各种形式的交互性和巧妙性。
值得注意的是,JavaScript 与名为 Java 的编程语言几乎没有任何关系。这个相似的名称是出于营销考虑,而不是出于良好的判断。JavaScript 推出时,Java 语言正在大力推广,越来越受欢迎。有人认为尝试利用这种成功是一个好主意。现在我们只能使用这个名字了。
在 Netscape 之外采用 JavaScript 语言后,人们编写了一份标准文档来描述 JavaScript 的工作方式,以便声称支持 JavaScript 的各种软件能够确保它们提供的是同一种语言。这被称为 ECMAScript 标准,以实施该标准化的 Ecma International 组织命名。实际上,ECMAScript 和 JavaScript 这两个术语可以互换使用 — 它们是同一种语言的两个名称。
有些人会说 JavaScript 的坏话。这些话很多都是真的。当我第一次被要求用 JavaScript 写一些东西时,我很快就开始鄙视它。它几乎可以接受我输入的任何东西,但解释的方式却与我的意思完全不同。当然,这很大程度上与我根本不知道自己在做什么有关,但这里确实存在一个问题:JavaScript 允许的东西太过自由。这种设计背后的想法是,它会让初学者更容易用 JavaScript 编程。实际上,它主要让你更难在程序中发现问题,因为系统不会向你指出这些问题。
不过,这种灵活性也有其优点。它为更严格的语言中不可能实现的技术留出了空间,并形成了一种令人愉快、非正式的编程风格。在正确学习了这门语言并使用它一段时间后,我开始真正喜欢上了 JavaScript。
JavaScript 有过多个版本。在 JavaScript 崛起为主流的时期(大约在 2000 年至 2010 年之间),ECMAScript 版本 3 是得到广泛支持的版本。在此期间,雄心勃勃的版本 4 正在开发中,该版本计划对该语言进行一系列彻底的改进和扩展。以如此激进的方式改变一种现存的、广泛使用的语言在政治上被证明是困难的,版本 4 的开发工作于 2008 年被放弃。一个不那么雄心勃勃的版本 5 于 2009 年问世,它只做了一些无争议的改进。2015 年,版本 6 问世,这是一个重大更新,其中包括版本 4 中计划的一些想法。从那时起,我们每年都会有新的小更新。
JavaScript 不断发展,这意味着浏览器必须不断跟上。如果你使用的是旧版浏览器,它可能不支持所有功能。语言设计者会小心谨慎,不会做出任何可能破坏现有程序的更改,因此新浏览器仍可以运行旧程序。在本书中,我使用的是 2024 版 JavaScript。
Web 浏览器并不是唯一使用 JavaScript 的平台。一些数据库(如 MongoDB 和 CouchDB)使用 JavaScript 作为其脚本和查询语言。一些用于桌面和服务器编程的平台(最著名的是 Node.js 项目(第 20 章的主题))提供了在浏览器之外编程 JavaScript 的环境。
代码及其用途
代码是组成程序的文本。本书的大多数章节都包含大量代码。我相信阅读代码和编写代码是学习编程不可或缺的部分。尽量不要只是浏览示例 - 认真阅读并理解它们。一开始这可能很慢而且令人困惑,但我保证你很快就会掌握它。练习也是如此。在你真正编写出可行的解决方案之前,不要以为你理解了它们。
我建议你在实际的 JavaScript 解释器中尝试练习解决方案。这样,你就能立即得到反馈,了解你所做的是否有效,并且我希望你会被诱惑去尝试并超越练习。
当您在浏览器中阅读本书时,您可以通过单击来编辑(和运行)所有示例程序。
在本书网站之外运行本书中定义的程序需要小心。许多示例都是独立的,应该可以在任何 JavaScript 环境中运行。但后面章节中的代码通常是为特定环境(浏览器或 Node.js)编写的,只能在那里运行。此外,许多章节定义了更大的程序,其中出现的代码片段相互依赖或依赖于外部文件。网站上的沙盒提供了指向 ZIP 文件的链接,其中包含运行给定章节代码所需的所有脚本和数据文件。
本书概述
本书大致包含三部分。前 12 章讨论 JavaScript 语言。接下来的 7 章介绍 Web 浏览器及其使用 JavaScript 进行编程的方式。最后,两章专门介绍 Node.js,这是另一种 JavaScript 编程环境。本书有五个项目章节,描述了更大的示例程序,让您体验实际编程的乐趣。
本书的语言部分以介绍 JavaScript 语言基本结构的四章开始。它们讨论了控制结构(例如 while 您在本介绍中看到的单词)、函数(编写自己的构建块)和数据结构。之后,您将能够编写基本的程序。接下来,第 5 章和第 6 章介绍了使用函数和对象编写更抽象的代码并控制复杂性的技术。
在第一个项目章节构建了一个粗糙的送货机器人之后,本书的语言部分继续介绍错误处理和错误修复、正则表达式(处理文本的重要工具)、模块化(另一种防御复杂性的方法)和异步编程(处理需要时间的事件)。第二个项目章节,我们实现了一种编程语言,结束了本书的第一部分。
本书的第二部分,第 13 章至第 19 章,介绍了浏览器 JavaScript 可以使用的工具。您将学习如何在屏幕上显示内容(第 14 章和第 17 章)、响应用户输入(第 15 章)以及通过网络进行通信(第 18 章)。本部分同样有两个项目章节:构建平台游戏和像素绘画程序。
第 20 章介绍了 Node.js,第 21 章使用该工具构建了一个小型网站。
排版惯例
在本书中,用 monospaced 字体书写的文本将表示程序的元素。有时这些是自足的片段,有时它们只是引用附近程序的一部分。程序(您已经看过一些)的书写方式如下:
function factorial(n) { if (n == 0) { return 1; } else { return factorial(n - 1) * n; }}有时,为了显示程序产生的输出,预期输出写在其后面,前面带有两个斜线和一个箭头。
console.log(factorial(8));// → 40320祝你好运!