第 1 章: 简介

当个人电脑首次出现时,大多数电脑都配备了一种简单的编程语言,通常是 BASIC 的变体。与电脑的交互与这种语言紧密结合,因此每个电脑用户,无论他们是否愿意,都会体验到编程。如今,电脑已经变得普及且廉价,普通用户不会比用鼠标点击更深入地接触电脑。对大多数人来说,这很好。但对于我们这些天生就喜欢技术摆弄的人来说,编程从日常电脑使用中消失,造成了一些阻碍。

幸运的是,由于万维网发展的影响,每台配备现代网页浏览器的电脑,也都有一个用于编程 JavaScript 的环境。在当今不打扰用户技术细节的精神下,它被很好地隐藏起来,但网页可以使它变得可访问,并将其用作学习编程的平台。

这就是这本书(超链接书)尝试做的事情。


我不启发那些不愿学习的人,也不唤醒那些不愿自己解释的人。如果我已经展示了正方形的一个角,而他们不能从其他三个角回到我这里,我就不会再重复这些要点。

― 孔子

除了解释 JavaScript,这本书还试图介绍编程的基本原理。事实证明,编程很难。大多数情况下,基本规则都很简单明了。但程序虽然建立在这些基本规则之上,但往往会变得足够复杂,以至于引入自己的规则,自己的复杂性。正因为如此,编程很少是简单或可预测的。正如计算机科学领域的奠基人唐纳德·克努特所说,它是一门**艺术**。

要从这本书中获得一些东西,仅仅被动阅读是不够的。试着保持敏锐,努力解决练习,只有当你对之前学习的材料有合理的理解时,才能继续学习。


计算机程序员是创造宇宙的人,他独自负责这些宇宙。形式为计算机程序的宇宙可以具有无限的复杂性。

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

程序是多种事物。它是程序员键入的一段文本,它是驱动电脑执行操作的指令,它是电脑内存中的数据,但它控制着对该内存执行的操作。试图将程序与我们熟悉的物体进行比较的类比往往会落空,但一个表面上合适的类比是机器。机械表中的齿轮巧妙地配合在一起,如果制表师技艺精湛,它将准确地显示时间很多年。程序的元素以类似的方式配合在一起,如果程序员知道自己在做什么,程序就会运行而不会崩溃。

电脑是一台机器,它被构建为这些非物质机器的宿主。电脑本身只能做愚蠢的直接的事情。它们之所以如此有用,是因为它们以惊人的速度执行这些操作。程序可以通过巧妙地组合许多这些简单的操作,来完成非常复杂的事情。

对我们中的一些人来说,编写计算机程序是一场迷人的游戏。程序是思想的建筑。它建造起来没有成本,没有重量,在我们的打字手中很容易成长。如果我们沉迷其中,它的规模和复杂性会失控,甚至会让创造它的人感到困惑。这是编程的主要问题。这就是为什么如今许多软件容易崩溃、失败、出错。

当程序运行时,它很美。编程的艺术是控制复杂性的技巧。伟大的程序是沉稳的,在复杂性中变得简单。


如今,许多程序员认为,这种复杂性最好通过在他们的程序中仅使用一组经过良好理解的技术来管理。他们制定了关于程序形式应该如何的严格规则,那些更热心的人会谴责违反这些规则的人为**糟糕的**程序员。

对编程的丰富性多么怀有敌意!试图将其简化为简单和可预测的东西,禁止所有奇怪而美丽的程序。编程技术的领域是巨大的,其多样性令人着迷,但仍然很大程度上没有被探索。它肯定布满了陷阱和圈套,诱使经验不足的程序员犯下各种可怕的错误,但这仅仅意味着你应该谨慎行事,保持清醒的头脑。当你学习时,总会有新的挑战,新的领域需要探索。拒绝继续探索的程序员肯定会停滞不前,忘记他的快乐,失去编程的意志(并成为一名经理)。

就我而言,判断一个程序是否好的明确标准是它是否正确。效率、清晰度和大小也很重要,但如何在这三者之间权衡始终是一个判断问题,每个程序员都必须自己做出判断。经验法则是有用的,但不要害怕打破它们。


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

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. 将内存位置 0 中的值加上内存位置 1 中的值
  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 加 1。

以下是 JavaScript 中的相同程序

var total = 0, count = 1;
while (count <= 10) {
  total += count;
  count += 1;
}
print(total);

这给我们带来了几个改进。最重要的是,不再需要指定程序来回跳转的方式。神奇的词 while 会处理这件事。它会继续执行下面的行,只要它被给定的条件成立:count <= 10,这意味着“count 小于或等于 10”。显然,不再需要创建临时值并将其与零进行比较。这是一个愚蠢的小细节,编程语言的强大之处在于它们为我们处理了这些愚蠢的小细节。

最后,如果我们碰巧拥有便捷的操作 rangesum,分别创建指定范围内的数字集合和计算数字集合的总和,程序看起来会像这样

print(sum(range(1, 10)));

因此,这个故事的寓意是,同一个程序可以用长短、不可读和可读的方式表达。程序的第一个版本非常晦涩,而最后一个版本几乎是英文:print110 的数字的 sum。(我们将在后面的章节中看到如何构建 sumrange 之类的东西。)

一种好的编程语言通过提供更抽象的方式来表达自己,从而帮助程序员。它隐藏了不重要的细节,提供了便捷的构建块(如 while 结构),并且大多数情况下允许程序员自己添加构建块(如 sumrange 操作)。


JavaScript 是当前主要用于对万维网上的页面执行各种巧妙和可怕操作的语言。一些 声称,JavaScript 的下一个版本也将成为其他任务的重要语言。我不确定是否会发生这种情况,但如果你对编程感兴趣,JavaScript 绝对是一种值得学习的有用语言。即使你最终没有做太多网页编程,我在本书中展示的那些令人头脑发麻的程序,将会永远伴随你,困扰你,并影响你在其他语言中编写的程序。

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

然而,这种语言的灵活性也是一种优势。它为在更严格的语言中不可能实现的许多技术留下了空间,并且可以用来克服 JavaScript 的一些缺点。在正确学习并使用了一段时间后,我真正学会了喜欢这种语言。


与名称所暗示的相反,JavaScript 与名为 Java 的编程语言几乎没有关系。相似的名称是出于营销考虑,而不是明智的选择。1995 年,Netscape 推出 JavaScript 时,Java 语言正在大力推广,并且越来越受欢迎。显然,有人认为尝试搭乘这种营销的顺风车是个好主意。现在我们被这个名字困住了。

与 JavaScript 相关的是一个名为 ECMAScript 的东西。当除 Netscape 之外的浏览器开始支持 JavaScript 或类似的东西时,编写了一个文档来精确地描述语言的工作方式。这个文档中描述的语言称为 ECMAScript,以标准化它的组织命名。

ECMAScript 描述了一种通用编程语言,并且没有说明这种语言在 Internet 浏览器中的集成方式。JavaScript 是 ECMAScript 加上用于处理 Internet 页面和浏览器窗口的额外工具。

其他一些软件使用了 ECMAScript 文档中描述的语言。最重要的是,Flash 使用的 ActionScript 语言基于 ECMAScript(尽管它并不完全遵循标准)。Flash 是一个用于在网页中添加移动并发出大量噪音的事物的系统。如果你发现自己正在学习制作 Flash 动画,了解 JavaScript 不会有什么坏处。

JavaScript 仍在不断发展。自从这本书出版以来,ECMAScript 5 已发布,它与这里描述的版本兼容,但添加了一些我们将在本章节中编写成内置方法的功能。最新一代的浏览器提供了这个扩展版本的 JavaScript。截至 2011 年,"ECMAScript Harmony",一种更激进的语言扩展,正在标准化过程中。你不必太担心这些新版本会使你从这本书中学习到的东西过时。首先,它们将是我们现在所拥有的语言的扩展,所以这本书中几乎所有内容仍然有效。


本书中的大多数章节都包含大量的代码1。根据我的经验,阅读和编写代码是学习编程的重要组成部分。尝试不要仅仅浏览这些示例,而是仔细阅读并理解它们。这在最初可能很慢且令人困惑,但你很快就会掌握它。练习也是如此。在你真正写出有效的解决方案之前,不要认为你理解它们。

由于网络的工作方式,始终可以查看人们放在网页中的 JavaScript 程序。这可能是学习某些操作方法的一个好方法。由于大多数网络程序员不是"专业"程序员,或者认为 JavaScript 编程非常枯燥乏味,以至于他们从未真正学习过它,因此你可以找到的大量这样的代码质量非常差。当你从丑陋或错误的代码中学习时,丑陋和混乱会传播到你的代码中,所以要小心你从谁那里学习。


为了让你能够尝试运行程序,包括示例和你自己编写的代码,本书使用了一个名为 控制台的东西。如果你使用的是现代图形浏览器(Internet Explorer 6 或更高版本,Firefox 1.5 或更高版本,Opera 9 或更高版本,Safari 3 或更高版本,Chrome),本书中的页面将在屏幕底部显示一个蓝色的条形。你可以通过单击此条形最右侧的小箭头来打开控制台。(请注意,我指的不是你浏览器的内置控制台。)

控制台包含三个重要元素。有一个输出窗口,用于显示错误消息和程序打印的内容。在下方,有一行,你可以在其中输入一段 JavaScript。尝试输入一个数字,然后按回车键来运行你输入的内容。如果你输入的文本产生了有意义的东西,它将显示在输出窗口中。现在尝试输入wrong!,然后再次按回车键。输出窗口将显示一条错误消息。你可以使用向上箭头和向下箭头键返回你之前输入的命令。

对于较大的代码段,那些跨越多行并且你想保留一段时间的内容,可以使用右侧的字段。"运行"按钮用于执行在此字段中编写的程序。可以同时打开多个程序。使用"新建"按钮打开一个新的空缓冲区。当有多个打开的缓冲区时,"运行"按钮旁边的菜单可用于选择要显示的缓冲区。"关闭"按钮顾名思义,用于关闭缓冲区。

本书中的示例程序始终在其右上角有一个带箭头的按钮,可以用来运行它们。我们之前看到的示例如下

var total = 0, count = 1;
while (count <= 10) {
  total += count;
  count += 1;
}
print(total);

通过单击箭头来运行它。还有一个按钮,用于将程序加载到控制台中。不要犹豫,修改它并尝试结果。最坏的情况是你创建了一个无限循环。无限循环是当while的条件永远不会变为假时得到的结果,例如,如果你选择向计数变量添加0而不是1。现在程序将永远运行。

幸运的是,浏览器会监视运行在其中的程序。只要其中一个程序花费的时间过长,它们就会询问你是否要停止它。


在后面的章节中,我们将构建由许多代码块组成的示例程序。通常情况下,你必须运行它们中的每一个,程序才能正常工作。你可能已经注意到,代码块中的箭头在运行该块后会变成紫色。阅读章节时,尝试运行遇到的每个代码块,尤其是那些"定义"新事物的代码块(你将在下一章中了解这意味着什么)。

当然,你可能无法一次性阅读完一整章。这意味着你必须从中途开始继续阅读,但如果你没有从章节开头开始运行所有代码,那么有些东西可能无法正常工作。通过在按代码块上的"运行"箭头时按住 Shift 键,将运行该块之前的所有块,因此当你从章节中间开始时,第一次运行代码段时按住 Shift 键,所有内容都应该按预期工作。

  1. "代码"是构成程序的物质。程序的每一部分,无论是单行还是整个程序,都可以称为"代码"。