第二章: 基础 JavaScript:值、变量和控制流

在计算机的世界里,只有数据。非数据者,皆无存在。虽然所有数据本质上只是一系列位1,因此在本质上是一样的,但每条数据都扮演着不同的角色。在 JavaScript 系统中,大部分数据被整齐地分离成称为 值的实体。每个值都有一个类型,决定了它可以扮演的角色。有六种基本的值类型:数字、字符串、布尔值、对象、函数和未定义值。

要创建一个值,只需调用它的名称即可。这非常方便。你无需为你的值收集构建材料,也不需要为它们付费,你只需呼叫一个,然后,你就有它了。当然,它们不是凭空产生的。每个值都必须存储在某个地方,如果你想同时使用大量的值,你可能会耗尽计算机内存。幸运的是,只有在你需要同时使用所有这些值时,这才会成为问题。只要你不再使用一个值,它就会消散,只留下几个位。这些位将被回收,用来创建下一代值。


类型为 数字的值,正如你可能推断的那样,是数值。它们以我们通常写数字的方式写出来

144

在控制台中输入这些内容,就会在输出窗口中打印出相同的内容。你输入的文本产生了数字值,控制台将这个数字再次写入屏幕。在这种情况下,这是一个相当无意义的操作,但很快我们将以不那么直接的方式生成值,在控制台中“测试它们”以查看它们会产生什么结果会很有用。

这就是 144 在位表示中的样子2

0100000001100010000000000000000000000000000000000000000000000000

上面的数字有 64 位。JavaScript 中的数字总是这样。这有一个重要的影响:可以表示的数字数量有限。使用三位小数,只能写出 0 到 999 之间的数字,即 103 = 1000 个不同的数字。使用 64 位二进制数,可以写出 264 个不同的数字。这很多,超过 1019(一个后面跟着十九个零)。

但是,并不是所有小于 1019 的整数都适合 JavaScript 数字。首先,还有负数,因此必须使用一位来存储数字的符号。一个更大的问题是,也必须表示非整数。为此,使用 11 位来存储小数点在数字中的位置。

这留下了 52 位3。任何小于 252(超过 1015)的整数都可以安全地放入 JavaScript 数字中。在大多数情况下,我们使用的数字远远小于这个值,因此我们根本不需要关心位。这很好。我对位没有特别的偏见,但你确实需要大量的位才能完成任何事情。只要有可能,处理更大的事物会更令人愉快。

小数通过使用小数点来写。

9.81

对于非常大或非常小的数字,还可以使用“科学”记数法,添加一个 e,后跟数字的指数

2.998e8

也就是 2.998 * 108 = 299800000。

对适合 52 位的整数进行的计算保证总是精确的。不幸的是,对小数的计算通常不精确。就像 π(pi)无法用有限数量的小数位精确地表示一样,当只有 64 位可用存储时,许多数字会失去一些精度。这很可惜,但它只会在非常特定的情况下引起实际问题。重要的是要意识到这一点,并将小数数字视为近似值,而不是精确值。


对数字进行的主要操作是算术运算。算术运算,如加法或乘法,将两个数值作为输入,并从中产生一个新的数字。以下是它们在 JavaScript 中的样子

100 + 4 * 11

+* 符号称为运算符。第一个代表加法,第二个代表乘法。在两个值之间放置一个运算符将 将它应用于这些值,并产生一个新值。

这个例子是“将 4 和 100 相加,并将结果乘以 11”,还是乘法在加法之前完成?正如你可能猜到的那样,乘法先发生。但是,就像在数学中一样,可以通过将加法放在括号中来改变这一点

(100 + 4) * 11

对于减法,可以使用 - 运算符,除法可以使用 / 完成。当运算符没有括号地一起出现时,它们应用的顺序由 运算符的优先级决定。第一个例子表明,乘法的优先级高于加法。除法和乘法总是先于减法和加法。当多个具有相同优先级的运算符彼此相邻出现时(1 - 1 + 1),它们从左到右应用。

试着找出这会产生什么值,然后运行它,看看你是否正确......

115 * 4 - 4 + 88 / 2

你不应该担心这些优先级规则。有疑问时,只需添加括号即可。

还有一个算术运算符可能不太熟悉。 % 符号用于表示 取余操作。X % YX 除以 Y 的余数。例如,314 % 1001410 % 31,而 144 % 120。取余的优先级与乘法和除法相同。


下一个数据类型是 字符串。它的用途不像数字那样从它的名称中明显看出,但它也扮演着非常基本的角色。字符串用于表示文本,其名称据说是源于它将一组字符串在一起的事实。字符串通过将它们的内容放在引号中来写

"Patch my boat with chewing gum."

几乎任何东西都可以放在双引号之间,JavaScript 会将它转换为字符串值。但是,有一些字符很棘手。你可以想象,将引号放在引号之间可能会很困难。换行符,你在按下回车键时得到的那些东西,也不能放在引号之间,字符串必须保持在一行上。

为了能够在字符串中包含这样的字符,使用以下技巧:只要在引号文本中找到反斜杠('\'),就表示它后面的字符具有特殊含义。在反斜杠之前的引号不会结束字符串,而是作为字符串的一部分。当在反斜杠之后出现 'n' 字符时,它被解释为换行符。类似地,在反斜杠之后出现的 't' 表示制表符4.

"This is the first line\nAnd this is the second"

请注意,如果你在控制台中输入此内容,它会以“源代码”形式将其显示回来,带有引号和反斜杠转义。要只查看实际文本,可以输入 print("a\nb")。更进一步解释一下,究竟是什么,将在后面的内容中进行说明。

当然,有些情况下你希望字符串中的反斜杠只是一个反斜杠,而不是特殊代码。如果两个反斜杠相继出现,它们会相互合并,结果字符串值中只保留一个

"A newline character is written like \"\\n\"."

字符串不能除、乘或减。 + 运算符可以用于它们。它不加,而是连接,它将两个字符串粘在一起。

"con" + "cat" + "e" + "nate"

还有更多操作字符串的方法,但这些方法将在后面讨论。


并非所有运算符都是符号,有些是作为单词写成的。例如, typeof 运算符,它会生成一个字符串值,用于命名你给它的值的类型。

typeof 4.5

我们看到的其他运算符都作用于两个值,typeof 只能作用于一个值。使用两个值的运算符称为 二元运算符,而使用一个值的运算符称为 一元运算符。 减号运算符可以用作二元运算符和一元运算符

- (10 - 2)

然后是 布尔值类型的值。只有两个:truefalse。以下是一种生成 true 值的方法

3 > 2

false 可以这样生成

3 < 2

我希望你以前见过 >< 符号。它们分别表示“大于”和“小于”。它们是二元运算符,应用它们的结果是一个布尔值,表示它们在这种情况下是否成立。

字符串可以用相同的方式进行比较

"Aardvark" < "Zoroaster"

字符串的排序方式或多或少是字母顺序的。或多或少......大写字母总是“小于”小写字母,因此 "Z" < "a"(大写 Z,小写 a)是 true,非字母字符('!','@' 等)也被包含在排序中。比较的实际方式是基于 Unicode 标准。这个标准为几乎所有你可能需要的字符都分配了一个数字,包括希腊语、阿拉伯语、日语、泰米尔语等字符。在计算机内部存储字符串时,拥有这些数字非常实用——可以将它们表示为数字列表。在比较字符串时,JavaScript 只比较字符串中字符的数字,从左到右进行比较。

其他类似的操作符有 >=(大于或等于),<=(小于或等于),==(等于)和!=(不等于)。

"Itchy" != "Scratchy"
5e2 == 500

还有一些有用的操作可以应用于布尔值本身。JavaScript 支持三种逻辑运算符:。这些可以用来对布尔值进行“推理”。

&& 操作符代表逻辑。它是一个二元运算符,其结果只有在赋予它的两个值都为true时才为true

true && false

|| 是逻辑,如果赋予它的两个值中有一个为true,则它为true

true || false

写成一个感叹号,!,它是一个一元运算符,可以翻转赋予它的值,!truefalse!falsetrue


示例 2.1
((4 >= 6) || ("grass" != "green")) &&
   !(((12 * 2) == 144) && true)

这是真的吗?为了可读性,那里有很多不必要的括号。这个简单的版本意思一样

(4 >= 6 || "grass" != "green") &&
   !(12 * 2 == 144 && true)

是的,它是true。你可以像这样一步一步地减少它

(false || true) && !(false && true)
true && !false
true

我希望你注意到"grass" != "green"true。草可能是绿色的,但它不等于绿色。


何时需要括号并不总是很明显。在实践中,人们通常可以知道到目前为止我们看到的运算符中,|| 的优先级最低,然后是 &&,然后是比较运算符(>== 等),然后是其他运算符。这种选择方式是为了在简单的情况下尽可能减少括号的使用。


到目前为止,所有示例都像使用袖珍计算器一样使用语言。创建一些值,并对它们应用运算符以获得新值。像这样创建值是每个 JavaScript 程序的重要组成部分,但它只是一部分。生成值的代码段称为 表达式。直接写出的每个值(如 22"psychoanalysis")都是一个表达式。括号之间的表达式也是一个表达式。二元运算符作用于两个表达式,或一元运算符作用于一个表达式,也都是表达式。

还有几种构建表达式的其他方法,将在时机成熟时揭示。

存在比表达式更大的单位。它被称为 语句。程序被构建为语句列表。大多数语句以 分号 (;) 结尾。最简单的语句类型是带有分号的表达式。这是一个程序

1;
!false;

这是一个无用的程序。表达式可以只生成值,而语句只有在以某种方式改变世界时才算有意义。它可以将内容打印到屏幕上——这算得上改变世界——或者它可以以影响后续语句的方式改变程序的内部状态。这些变化被称为“副作用”。上面的示例中的语句只是生成值 1true,然后立即将它们丢弃到位桶中5。这对世界没有任何影响,也不是副作用。


程序如何保持内部状态?它如何记住事物?我们已经看到了如何从旧值中生成新值,但这不会改变旧值,而新值必须立即使用,否则它将再次消失。为了捕获和保存值,JavaScript 提供了一个名为 变量 的东西。

var caught = 5 * 5;

变量始终有一个名称,并且它可以指向一个值,并保存它。上面的语句创建了一个名为 caught 的变量,并使用它来抓住将 5 乘以 5 生成的数字。

在运行上面的程序之后,您可以在控制台中输入 caught 这个词,它将为您检索值 25。变量的名称用于获取它的值。caught + 1 也可行。变量名可以用作表达式,因此可以作为更大表达式的组成部分。

单词 var 用于创建一个新变量。在 var 之后,是变量的名称。变量名几乎可以是任何单词,但不能包含空格。数字可以作为变量名的一部分,catch22 是一个有效的名称,但名称不能以数字开头。字符“$”和“_”可以在名称中使用,就像字母一样,因此 $_$ 是一个正确的变量名。

如果您希望新变量立即捕获一个值,而这通常是情况,可以使用 = 运算符为它赋予某个表达式的值。

当一个变量指向一个值时,并不意味着它永远绑定到该值。在任何时候,= 运算符都可以用于现有变量,将它们从当前值中拉出来,并让它们指向一个新的值。

caught = 4 * 4;

您应该将变量想象成触手,而不是盒子。它们不包含值,它们抓住它们——两个变量可以引用同一个值。只有程序仍然持有的值才能被它访问。当您需要记住某件事时,您会伸出一根触手抓住它,或者重新连接您现有的触手之一到一个新值:要记住路易吉还欠您的美元金额,您可以这样做……

var luigiDebt = 140;

然后,每当路易吉还款时,可以通过给变量一个新数字来减少这个金额

luigiDebt = luigiDebt - 35;

在给定时间存在的变量及其值的集合被称为 环境。当程序启动时,这个环境并不为空。它总是包含一些标准变量。当您的浏览器加载页面时,它会创建一个新环境并将这些标准值附加到它。在该页面上程序创建和修改的变量会一直存在,直到浏览器转到新页面。


标准环境提供的许多值都具有“函数”类型。函数是包裹在值中的程序片段。通常,这个程序片段会做一些有用的事情,可以使用包含它的函数值来调用它。在浏览器环境中,变量 alert 持有一个函数,该函数会显示一个小对话框,其中包含一条消息。它像这样使用

alert("Avocados");

执行函数中的代码称为 调用、调用或 应用它。执行此操作的符号使用括号。每个生成函数值的表达式都可以通过在它后面加上括号来调用。在本例中,值 "Avocados" 被赋予该函数,该函数使用它作为在对话框中显示的文本。赋予函数的值称为 参数或 参数。alert 只需要其中一个,但其他函数可能需要不同的数量。


显示一个对话框是一个副作用。许多函数之所以有用是因为它们产生的副作用。函数也可以生成值,在这种情况下,它不需要有副作用才能有用。例如,有一个函数 Math.max,它接受任意数量的数字参数并返回最大的

alert(Math.max(2, 4));

当函数生成一个值时,据说它 返回它。因为在 JavaScript 中生成值的总是表达式,所以函数调用可以用作更大表达式的组成部分

alert(Math.min(2, 4) + 100);

第 3 章 讨论了编写自己的函数。


如前面的示例所示,alert 可用于显示某些表达式的结果。然而,一直点击掉所有这些小窗口可能会让人感到厌烦,因此从现在开始,我们将更喜欢使用一个类似的函数,称为 print,它不会弹出一个窗口,而只是将一个值写入控制台的输出区域。print 不是标准的 JavaScript 函数,浏览器不会为您提供它,但本书提供了它,因此您可以在这些页面上使用它。

print("N");

另一个类似的函数,也是在这些页面上提供的,是 show。虽然 print 会以纯文本形式显示其参数,但 show 会尝试以程序中的显示方式显示它,这可以提供有关该值类型的信息。例如,字符串值在被赋予 show 时会保留其引号

show("N");

浏览器提供的标准环境包含几个用于弹出窗口的其他函数。您可以使用 confirm 来询问用户一个确认/取消问题。这将返回一个布尔值,如果用户按下“确定”,则为 true,如果用户按下“取消”,则为 false

show(confirm("Shall we, then?"));

prompt 可用于询问一个“开放式”问题。第一个参数是问题,第二个参数是用户开始使用的文本。可以在窗口中键入一行文本,该函数会将其作为字符串返回。

show(prompt("Tell us everything you know.", "..."));

几乎可以为环境中的每个变量赋予一个新值。这很有用,但也可能很危险。如果您为 print 赋予值 8,那么您将无法再打印任何东西。幸运的是,控制台上有一个大的“重置”按钮,它会将环境重置为其原始状态。


单行程序并不有趣。 当你将多个语句放入程序中时,这些语句将按照预期从上到下依次执行。

var theNumber = Number(prompt("Pick a number", ""));
print("Your number is the square root of " +
      (theNumber * theNumber));

函数 Number 将值转换为数字,在本例中这是必要的,因为 prompt 的结果是字符串值。 还有类似的函数称为 StringBoolean ,它们将值转换为这些类型。


考虑一个打印从 0 到 12 的所有偶数的程序。 编写此程序的一种方法是

print(0);
print(2);
print(4);
print(6);
print(8);
print(10);
print(12);

这有效,但编写程序的目的是让工作量减少,而不是增加。 如果我们需要 1000 以下的所有偶数,上面的方法将不可行。 我们需要的是一种自动重复某些代码的方法。

var currentNumber = 0;
while (currentNumber <= 12) {
  print(currentNumber);
  currentNumber = currentNumber + 2;
}

你可能在介绍篇中见过 while 。 以 while 开头的语句会创建一个 循环。 循环是语句序列中的一个干扰——它可能导致程序多次重复某些语句。 在这种情况下,while 后面跟着一个括号中的表达式(这里的括号是强制性的),该表达式用于确定循环是循环还是结束。 只要此表达式产生的布尔值为 true ,循环中的代码就会重复。 一旦它为假,程序就会转到循环底部,并像往常一样继续执行。

变量 currentNumber 演示了变量如何跟踪程序的进度。 每次循环重复时,它都会增加 2 ,并且在每次重复开始时,它都会与数字 12 进行比较,以决定是否继续循环。

while 语句的第三部分是另一个语句。 这是循环的 主体,即必须多次执行的操作或操作。 如果我们不需要打印数字,该程序可以是

var currentNumber = 0;
while (currentNumber <= 12)
  currentNumber = currentNumber + 2;

这里,currentNumber = currentNumber + 2; 是构成循环主体的语句。 不过,我们也必须打印数字,因此循环语句必须包含多个语句。 花括号 ({}) 用于将语句分组到 块中。 对块外部来说,块相当于一个语句。 在前面的示例中,它用于在循环中包含对 print 的调用和更新 currentNumber 的语句。


例 2.2

使用迄今为止展示的技术编写一个程序,该程序计算并显示 210(2 的 10 次方)的值。 显然,你不允许使用像只写 2 * 2 * ... 这样的廉价技巧。

如果你对此有困难,试着从偶数示例的角度来看待它。 该程序必须执行某个操作一定次数。 带有 while 循环的计数器变量可以用于此。 该程序不需要打印计数器,而是必须将某个东西乘以 2。 这个东西应该是另一个变量,结果值会在其中累加。

如果你还不清楚它将如何工作,请不要担心。 即使你完全理解本章涵盖的所有技术,也很难将它们应用于特定问题。 阅读和编写代码将有助于培养这种感觉,因此请学习解决方案,并尝试下一个练习。

var result = 1;
var counter = 0;
while (counter < 10) {
  result = result * 2;
  counter = counter + 1;
}
show(result);

计数器也可以从 1 开始,并检查是否 <= 10 ,但由于某些稍后会变得明显的原因,习惯从 0 开始计数是一个好主意。

显然,你自己的解决方案不需要与我的完全相同。 它们应该起作用。 而且,如果它们大不相同,请确保你也理解我的解决方案。


例 2.3

进行一些细微修改后,前一个练习的解决方案可以用来画一个三角形。 我说“画一个三角形”的意思是“打印一些文本,当你眯着眼睛看时,它几乎像一个三角形”。

打印十行。 在第一行有一个 '#' 字符。 第二行有两个。 依此类推。

如何获得包含 X 个 '#' 字符的字符串? 一种方法是在每次需要时使用“内循环”(循环中的循环)来构建它。 更简单的方法是重用循环上一次迭代使用的字符串,并向其添加一个字符。

var line = "";
var counter = 0;
while (counter < 10) {
  line = line + "#";
  print(line);
  counter = counter + 1;
}

你可能已经注意到我在某些语句前面加了空格。 这些不是必需的:计算机将接受程序,即使没有这些空格。 事实上,程序中的换行符也是可选的。 如果你愿意,可以将它们写成一行。 缩进在块中的作用是使代码的结构对读者更加清晰。 由于新块可以在其他块内打开,因此在复杂的代码段中可能很难看到一个块在哪里结束,另一个块在哪里开始。 当行缩进时,程序的视觉形状对应于其内部块的形状。 我喜欢每个打开的块使用两个空格,但个人喜好不同。

控制台中你可以输入程序的区域会通过自动添加这些空格来帮助你。 这开始可能看起来很烦人,但是当你编写大量代码时,它会成为一个巨大的省时工具。 按 shift+tab 会重新缩进光标当前所在的行。

在某些情况下,JavaScript 允许你省略语句末尾的分号。 在其他情况下,它必须存在,否则会出现奇怪的情况。 关于何时可以安全省略它的规则很复杂而且很奇怪。 在本书中,我不会省略任何分号,我强烈建议你在自己的程序中也这样做。


我们迄今为止看到的 while 的用法都显示了相同的模式。 首先,创建一个“计数器”变量。 此变量跟踪循环的进度。 while 本身包含一个检查,通常是检查计数器是否已达到某个边界。 然后,在循环体末尾,更新计数器。

许多循环都符合这种模式。 出于这个原因,JavaScript 和类似语言还提供了一种稍微更短、更全面的形式

for (var number = 0; number <= 12; number = number + 2)
  show(number);

该程序与之前的偶数打印示例完全相同。 唯一的变化是所有与循环“状态”相关的语句现在都在一行上。 for 后面的括号应该包含两个分号。 第一个分号之前的部分初始化循环,通常是通过定义一个变量。 第二部分是表达式,它检查循环是否必须继续。 最后一部分更新循环的状态。 在大多数情况下,这比 while 结构更短、更清晰。


我一直在某些变量名中使用一些相当奇怪的 大小写。 因为这些名称中不能有空格——计算机会将它们读作两个独立的变量——你对由多个单词组成的名称的选择或多或少局限于以下几种:fuzzylittleturtlefuzzy_little_turtleFuzzyLittleTurtlefuzzyLittleTurtle。 第一个很难阅读。 就我个人而言,我喜欢带下划线的那一个,尽管它有点难打字。 但是,标准 JavaScript 函数和大多数 JavaScript 程序员都遵循最后一个。 这类小事并不难适应,所以我只会随大流,将第一个单词后的每个单词的首字母大写。

在一些情况下,比如 Number 函数,变量的第一个字母也大写。 这样做是为了将此函数标记为构造函数。 什么是构造函数将在 第 8 章中说明。 现在,重要的是不要被这种明显的缺乏一致性所困扰。

请注意,具有特殊含义的名称,例如 varwhilefor 不能用作变量名。 这些被称为 关键字。 还有一些 词语“保留供将来版本的 JavaScript 使用”。 从官方上讲,这些也不允许用作变量名,尽管某些浏览器确实允许它们。 完整的列表相当长

abstract boolean break byte case catch char class const continue
debugger default delete do double else enum export extends false
final finally float for function goto if implements import in
instanceof int interface long native new null package private
protected public return short static super switch synchronized
this throw throws transient true try typeof var void volatile
while with

现在不用担心记住这些,但请记住,当某些东西的行为不像预期时,这可能是问题所在。 根据我的经验,char(用于存储一个字符的字符串)和 class 是最常见的意外使用的名称。


例 2.4

将前两个练习的解决方案改写为使用 for 而不是 while

var result = 1;
for (var counter = 0; counter < 10; counter = counter + 1)
  result = result * 2;
show(result);

请注意,即使没有使用“{”打开任何块,循环中的语句也会缩进两个空格,以明确表示它“属于”上面的行。

var line = "";
for (var counter = 0; counter < 10; counter = counter + 1) {
  line = line + "#";
  print(line);
}

程序通常需要使用基于其先前值的变量来“更新”某个变量。 例如 counter = counter + 1 。 JavaScript 提供了一个快捷方式:counter += 1 。 这也适用于许多其他运算符,例如 result *= 2 用于将 result 的值加倍,或者 counter -= 1 用于向下计数。

对于 counter += 1counter -= 1 ,还有更短的版本:counter++counter--


据说循环会影响程序的 控制流。 它们会更改语句执行的顺序。 在许多情况下,另一种流很有用:跳过语句。

我们想要显示所有小于 20 且同时能被 3 和 4 整除的数字。

for (var counter = 0; counter < 20; counter++) {
  if (counter % 3 == 0 && counter % 4 == 0)
    show(counter);
}

关键字 if 与关键字 while 并没有太大区别:它检查给定的条件(在括号中),并根据此条件执行其后的语句。但它只执行一次,因此语句最多执行一次。

使用余数运算符 (%) 是测试一个数字是否能被另一个数字整除的简单方法。如果是,它们的除法余数(即余数运算符的返回值)为零。

如果我们想打印所有小于 20 的数字,但将不能被 4 整除的数字用括号括起来,我们可以这样做

for (var counter = 0; counter < 20; counter++) {
  if (counter % 4 == 0)
    print(counter);
  if (counter % 4 != 0)
    print("(" + counter + ")");
}

但现在程序必须两次确定 counter 是否能被 4 整除。通过在 if 语句后添加 else 部分可以获得相同的效果。只有当 if 的条件为假时,else 语句才会执行。

for (var counter = 0; counter < 20; counter++) {
  if (counter % 4 == 0)
    print(counter);
  else
    print("(" + counter + ")");
}

为了进一步扩展这个简单的例子,我们现在想打印这些相同的数字,但当它们大于 15 时在其后添加两个星号,当它们大于 10(但不大于 15)时添加一个星号,否则不添加星号。

for (var counter = 0; counter < 20; counter++) {
  if (counter > 15)
    print(counter + "**");
  else if (counter > 10)
    print(counter + "*");
  else
    print(counter);
}

这演示了您可以将 if 语句链接在一起。在这种情况下,程序首先查看 counter 是否大于 15。如果是,则打印两个星号并跳过其他测试。如果不是,则继续检查 counter 是否大于 10。只有当 counter 也小于或等于 10 时,才会到达最后一个 print 语句。


例 2.5

编写一个程序,使用 prompt 向自己提问 2 + 2 的值。如果答案是“4”,使用 alert 说一些赞美的话。如果是“3”或“5”,则说“几乎对了!”。在其他情况下,说一些难听的话。

var answer = prompt("You! What is the value of 2 + 2?", "");
if (answer == "4")
  alert("You must be a genius or something.");
else if (answer == "3" || answer == "5")
  alert("Almost!");
else
  alert("You're an embarrassment.");

当循环不需要始终执行到最后时,break 关键字很有用。它会立即跳出当前循环,并在循环结束后继续执行。这个程序找到第一个大于或等于 20 且能被 7 整除的数字

for (var current = 20; ; current++) {
  if (current % 7 == 0)
    break;
}
print(current);

上面显示的 for 结构没有检查循环结束的部分。这意味着它依赖于内部的 break 语句来停止。相同的程序也可以简单地写成...

for (var current = 20; current % 7 != 0; current++)
  ;
print(current);

在这种情况下,循环体是空的。可以使用一个单独的分号来生成一个空语句。这里,循环的唯一作用是将变量 current 递增到其期望的值。但我需要一个使用 break 的例子,所以请注意第一个版本。


例 2.6

在之前练习的解决方案中添加一个 while,可以选择添加一个 break,以便它不断重复提问,直到给出正确答案。

注意,while (true) ... 可用于创建不自行结束的循环。这有点愚蠢,您要求程序循环,只要 truetrue,但这是一个有用的技巧。

var answer;
while (true) {
  answer = prompt("You! What is the value of 2 + 2?", "");
  if (answer == "4") {
    alert("You must be a genius or something.");
    break;
  }
  else if (answer == "3" || answer == "5") {
    alert("Almost!");
  }
  else {
    alert("You're an embarrassment.");
  }
}

由于第一个 if 的主体现在有两个语句,我在所有主体周围添加了大括号。这是一种个人喜好问题。我认为拥有一个 if/else 链,其中一些主体是块而另一些是单个语句看起来有点不平衡,但您可以自己决定。

另一个解决方案,可以说更漂亮,但没有 break

var value = null;
while (value != "4") {
  value = prompt("You! What is the value of 2 + 2?", "");
  if (value == "4")
    alert("You must be a genius or something.");
  else if (value == "3" || value == "5")
    alert("Almost!");
  else
    alert("You're an embarrassment.");
}

在之前练习的解决方案中,有一条语句 var answer;。这会创建一个名为 answer 的变量,但不会赋予它一个值。当您获取此变量的值时会发生什么?

var mysteryVariable;
show(mysteryVariable);

就触手而言,这个变量在虚空中结束,它没有可以抓住的东西。当您请求一个空位置的值时,您会得到一个名为 undefined 的特殊值。不返回值的函数,如 printalert,也会返回 undefined 值。

show(alert("I am a side effect."));

还有一个类似的值,null,它的含义是“此变量已定义,但没有值”。undefinednull 之间的含义差异主要是学术性的,通常并不十分有趣。在实际程序中,通常需要检查某物是否“有值”。在这些情况下,可以使用表达式 something == undefined,因为即使它们不完全相同,null == undefined 也会产生 true


这引出了另一个棘手的话题...

show(false == 0);
show("" == 0);
show("5" == 5);

所有这些都给出 true 值。在比较具有不同类型的数值时,JavaScript 使用一套复杂且令人困惑的规则。我不会尝试精确地解释它们,但在大多数情况下,它只是试图将其中一个数值转换为另一个数值的类型。但是,当出现 nullundefined 时,只有当两边都是 nullundefined 时,它才会产生 true

如果您想测试一个变量是否引用 false 值,该怎么办?将字符串和数字转换为布尔值的规则规定,0 和空字符串被视为 false,而所有其他值被视为 true。因此,当 variable 引用 0"" 时,表达式 variable == false 也是 true。对于这种情况,您不希望发生任何自动类型转换,有两个额外的运算符:===!==。第一个测试一个值是否精确等于另一个值,而第二个测试它是否不精确等于。

show(null === undefined);
show(false === 0);
show("" === 0);
show("5" === 5);

所有这些都是 false


ifwhilefor 语句中作为条件给出的数值不必是布尔值。它们会在被检查之前自动转换为布尔值。这意味着数字 0、空字符串 ""nullundefined,当然还有 false,都会被视为假。

在这种情况下,所有其他值都被转换为 true 的事实使得在许多情况下可以省略显式比较。如果已知一个变量包含字符串或 null,可以非常简单地检查这一点...

var maybeNull = null;
// ... mystery code that might put a string into maybeNull ...
if (maybeNull)
  print("maybeNull has a value");

... 除了当神秘代码将 maybeNull 赋予 "" 值的情况下。空字符串为假,因此不会打印任何内容。根据您要执行的操作,这可能是错误的。在这样的情况下,通常最好添加一个显式的 === null=== false 来防止细微的错误。同样的事情也会发生在可能为 0 的数字值上。


之前示例中提到“神秘代码”的那一行可能看起来有点可疑。在程序中包含额外的文本通常很有用。最常见的用途是在程序中添加一些用人类语言解释的说明。

// The variable counter, which is about to be defined, is going
// to start with a value of 0, which is zero.
var counter = 0;
// Now, we are going to loop, hold on to your hat.
while (counter < 100 /* counter is less than one hundred */)
/* Every time we loop, we INCREMENT the value of counter,
   Seriously, we just add one to it. */
  counter++;
// And then, we are done.

这种类型的文本称为 注释。规则如下:“/*”开始一个注释,一直持续到找到“*/”。“//”开始另一种注释,一直持续到行尾。

如您所见,即使是最简单的程序也可以通过简单地向其中添加大量注释,使其看起来很大、难看、复杂。


还有一些其他情况会导致自动 类型转换。如果您将非字符串值添加到字符串中,则该值会在连接之前自动转换为字符串。如果您将数字和字符串相乘,JavaScript 会尝试将字符串转换为数字。

show("Apollo" + 5);
show(null + "ify");
show("5" * 5);
show("strawberry" * 5);

最后一个语句打印 NaN,这是一个特殊的值。它代表“非数字”,并且是数字类型(这可能听起来有点矛盾)。在这种情况下,它指的是草莓不是数字这一事实。对 NaN 值执行的所有算术运算都会导致 NaN,这就是为什么在示例中将其乘以 5 仍然得到 NaN 值的原因。此外,并且这有时会令人困惑,NaN == NaN 等于 false,可以使用 isNaN 函数检查一个值是否为 NaNNaN 是另一个(最后一个)值,在转换为布尔值时被视为 false

这些自动转换非常方便,但它们也很奇怪,容易出错。即使 +* 都是算术运算符,它们在示例中的行为完全不同。在我的代码中,我经常使用 + 来组合字符串和非字符串,但我尽量避免对字符串值使用 * 和其他数字运算符。将数字转换为字符串始终是可能的,而且很简单,但将字符串转换为数字可能无法正常工作(如示例中的最后一行)。我们可以使用 Number 显式地将字符串转换为数字,使其清楚地表明我们可能会冒着获得 NaN 值的风险。

show(Number("5") * 5);

当我们之前讨论布尔运算符 &&|| 时,我声称它们会生成布尔值。事实证明,这有点过于简单。如果您将它们应用于布尔值,它们确实会返回布尔值。但它们也可以应用于其他类型的数值,在这种情况下,它们会返回它们的其中一个参数。

|| 的实际作用是:它首先查看其左侧的值。如果将此值转换为布尔值会产生 true,它会返回此左侧的值,否则它会返回其右侧的值。自己检查一下当参数是布尔值时,它是否能正常工作。为什么它会这样工作?事实证明,这非常实用。考虑以下示例

var input = prompt("What is your name?", "Kilgore Trout");
print("Well hello " + (input || "dear"));

如果用户按下“取消”或以其他方式关闭 prompt 对话框,而没有提供姓名,则变量 input 将保存 null"" 值。这两个值在转换为布尔值时都会得到 false。表达式 input || "dear" 在这种情况下可以理解为“变量 input 的值,否则为字符串 "dear"”。这是一种提供“备用”值的简单方法。

&& 运算符的工作方式类似,但方向相反。当其左侧的值在转换为布尔值时会得到 false,则返回该值,否则返回其右侧的值。

这两个运算符的另一个特性是,它们右侧的表达式只有在必要时才进行求值。在 true || X 的情况下,无论 X 是什么,结果都将是 true,因此 X 永远不会被求值,如果它有副作用,它们永远不会发生。false && X 也是如此。

false || alert("I'm happening!");
true || alert("Not me.");
  1. 位是任何类型的二进制值,通常描述为 01。在计算机内部,它们采用高低电荷、强弱信号、CD 表面上的光亮或暗淡斑点等形式。
  2. 如果你期望在这里看到类似 10010000 的东西——没错,但继续读下去。JavaScript 的数字不是存储为整数。
  3. 实际上是 53,因为可以使用一个技巧免费获得一位。如果你想知道细节,请查找“IEEE 754”格式。
  4. 当你在控制台中输入字符串值时,你会注意到它们会以你输入的方式显示引号和反斜杠。要使特殊字符正常显示,你可以执行 print("a\nb")——我们将在下一节中看到这是为什么。
  5. 位桶据说是存放旧位的地方。在某些系统中,程序员必须手动清空它。幸运的是,JavaScript 带有全自动位回收系统。