第二章 - 程序结构
-
在我薄薄的半透明皮肤下,我的心脏发出明亮的红色,他们必须注射10cc的JavaScript才能让我活过来。(我对血液中的毒素反应很好。)伙计,那东西会把桃子踢出你的鳃!
Why(Poignant)Ruby指南在本章中,我们将开始做一些实际上可以称为编程的事情。我们将扩展我们对JavaScript语言的命令,超越我们目前所见的名词和句子片段,直到我们能够表达有意义的散文。
表达和陈述
在第1章中,我们为它们创建值和应用运算符以获取新值。创建这样的值是任何JavaScript程序的主要内容。但是,这种物质必须以更大的结构框架才有用。这就是我们接下来会介绍的内容。
产生值的代码片段称为表达式。字面上写的每个值(例如
22
或"psychoanalysis"
)都是表达式。括号内的表达式也是一个表达式,应用于两个表达式的二元运算符或应用于一个表达式的一元运算符也是如此。这显示了基于语言的界面的美妙部分。表达式可以包含其他表达式,其方式类似于嵌套人类语言中的子句 - 子句可以包含自己的子句,等等。这允许我们构建描述任意复杂计算的表达式。
如果表达式对应于句子片段,则JavaScript 语句对应于完整句子。程序是一个语句列表。
最简单的语句是一个带分号的表达式。这是一个程序:
1 ; !false ;
不过,这是一个无用的程序。表达式可以是内容,只是生成一个值,然后可以由封闭代码使用。一个声明本身就是一个声明,所以只有当它影响世界时它才会成为某种东西。它可以在屏幕上显示某些内容 - 这可以改变世界 - 或者它可以改变机器的内部状态,从而影响其后的语句。这些变化称为副作用。前面例子中的陈述只是生产值1和true,然后立即扔掉。这根本不会给世界留下任何印象。运行此程序时,没有任何可观察的事件发生。
在某些情况下,JavaScript允许您在语句结尾处省略分号。在其他情况下,它必须在那里,或者下一行将被视为同一语句的一部分。何时可以安全省略的规则有些复杂且容易出错。所以在本书中,每个需要分号的语句总是会得到一个。我建议你这样做,至少在你学到更多关于丢失分号的微妙之处之前。
绑定
程序如何保持内部状态?它是如何记住的?我们已经看到了如何从旧值生成新值,但这不会改变旧值,并且必须立即使用新值或者它将再次消散。为了捕获和保存值,JavaScript提供了一个称为绑定或变量的东西:
let caught = 5 * 5 ;
这是第二种说法。特殊字(关键字)let表示该句子将定义绑定。接下来是绑定的名称,如果我们想立即通过=运算符和表达式给它一个值。
前一个语句创建一个被调用的绑定,caught并使用它来抓住5乘以5产生的数字。
定义绑定后,其名称可用作表达式。此类表达式的值是绑定当前保存的值。这是一个例子:
let ten = 10; console.log(ten * ten); // → 100
当绑定指向某个值时,这并不意味着它永远与该值绑定。该=操作员可以在任何时候使用现有的绑定,从他们的当前值断开它们并让它们指向一个新的。
let mood = "light"; console.log(mood); // → light mood = "dark"; console.log(mood); // → dark
你应该把绑定想象成触手而不是盒子。它们不包含值; 他们抓住它们 - 两个绑定可以引用相同的值。程序只能访问它仍然具有引用的值。当你需要记住某些东西时,你会长出一个触手来抓住它,或者你将一个现有的触手重新连接到它上面。
让我们看另一个例子。要记住Luigi仍然欠你的钱数,你就可以创建一个绑定。然后当他支付35美元时,你给这个绑定一个新的价值。
let luigisDebt = 140; luigisDebt = luigisDebt - 35; console.log(luigisDebt); // → 105
当你定义一个没有给它一个值的绑定时,触手没有什么可以抓住的,所以它在空气中结束。如果您要求空绑定的值,您将获得该值
undefined
。单个let语句可以定义多个绑定。必须用逗号分隔定义。
let one = 1, two = 2; console.log(one + two); // → 3
单词var和const也可用于创建绑定,方式类似于let。
var name = "Ayda"; const greeting = "Hello "; console.log(greeting + name); // → Hello Ayda
第一个
var
(“变量”的缩写)是在2015年之前的JavaScript中声明绑定的方式。我会回到它不同于具体的方法let在下一章。现在,请记住它主要做同样的事情,但我们很少在本书中使用它,因为它有一些令人困惑的属性。这个词const代表不变。它定义了一个常量绑定,只要它存在就指向相同的值。这对于为值赋予名称的绑定很有用,以便您以后可以轻松引用它。
绑定名称
绑定名称可以是任何单词。数字可以是绑定名称的一部分 - catch22例如是有效名称 - 但名称不能以数字开头。绑定名称可以包括美元符号($)或下划线(_),但不包括其他标点符号或特殊字符。
具有特殊含义的单词(例如let)是关键字,它们不能用作绑定名称。在将来的JavaScript版本中还有许多“保留使用”的单词,它们也不能用作绑定名称。关键字和保留字的完整列表相当长。
break case catch class const continue debugger default delete do else enum export extends false finally for function if implements import interface in instanceof let new package private protected public return static super switch this throw true try typeof var void while with yield
不要担心记住这个清单。创建绑定时会产生意外的语法错误,请查看是否要尝试定义保留字。
环境
在给定时间存在的绑定及其值的集合称为环境。程序启动时,此环境不为空。它总是包含作为语言标准一部分的绑定,并且大多数时候,它还具有提供与周围系统交互的方式的绑定。例如,在浏览器中,存在与当前加载的网站交互并读取鼠标和键盘输入的功能。
功能
默认环境中提供的许多值都具有type 函数。函数是一个包含在值中的程序。可以应用这些值以运行包装程序。例如,在浏览器环境中,绑定prompt包含一个函数,该函数显示一个要求用户输入的小对话框。它使用如下:
prompt("Enter passcode");
执行函数称为调用,调用或应用它。您可以通过在产生函数值的表达式后面加括号来调用函数。通常,您将直接使用包含该函数的绑定的名称。括号之间的值被赋予函数内的程序。在该示例中,该prompt函数使用我们提供的字符串作为要在对话框中显示的文本。赋予函数的值称为参数。不同的函数可能需要不同数量或不同类型的参数。
该prompt功能在现代Web编程中使用不多,主要是因为您无法控制生成的对话框的外观,但在玩具程序和实验中可能会有所帮助。
console.log函数
在示例中,我习惯console.log输出值。大多数JavaScript系统(包括所有现代Web浏览器和Node.js)都提供了console.log将其参数写入某些文本输出设备的函数。在浏览器中,输出落在JavaScript控制台中。默认情况下,这部分浏览器界面是隐藏的,但大多数浏览器在按F12时打开它,或者在Mac上打开命令 - 选项 -I。如果这不起作用,请在菜单中搜索名为Developer Tools或类似项目的项目。
在本书的页面上运行示例(或您自己的代码)时,console.log输出将显示在示例之后,而不是在浏览器的JavaScript控制台中。
let x = 30; console.log("the value of x is", x); // → the value of x is 30
虽然绑定名称不能包含句点字符,
console.log
但确实有一个。这是因为console.log
不是简单的绑定。它实际上是一个表达式,它log从console绑定所持有的值中检索属性。我们将在第4章中找到具体含义。返回值
显示对话框或将文本写入屏幕是一种副作用。由于它们产生的副作用,许多功能都很有用。函数也可以产生值,在这种情况下,它们不需要具有副作用。例如,该函数Math.max接受任意数量的数字参数并返回最大值。console.log(Math.max(2, 4)); // → 4
当函数产生一个值时,它会返回该值。产生值的任何东西都是JavaScript中的表达式,这意味着函数调用可以在更大的表达式中使用。这里调用Math.min,与之相反Math.max,用作加号表达式的一部分:
console.log(Math.min(2, 4) + 100); // → 102
在接下来的章节将介绍如何编写自己的函数。
控制流
当您的程序包含多个语句时,语句将从上到下执行,就好像它们是一个故事一样。此示例程序有两个语句。第一个询问用户一个数字,第二个在第一个之后执行,显示该数字的平方。
let theNumber = Number(prompt("Pick a number")); console.log("Your number is the square root of " + theNumber * theNumber);
该函数
Number
将值转换为数字。我们需要转换,因为结果prompt
是一个字符串值,我们想要一个数字。还有所谓的类似的功能String
和Boolean
该值转化为这些类型。条件执行
并非所有节目都是直道。例如,我们可能想要创建一条分支道路,程序根据手头的情况选择合适的分支。这称为条件执行。
使用
if
-JavaScript中的关键字创建条件执行。在简单的情况下,我们希望当且仅当某个条件成立时才执行某些代码。例如,我们可能只想在输入实际上是数字时才显示输入的平方。let theNumber = Number(prompt("Pick a number")); if (!Number.isNaN(theNumber)) { console.log("Your number is the square root of " + theNumber * theNumber); }
通过此修改,如果输入“parrot”,则不显示输出。
的if关键字执行或跳过根据布尔表达式的值的语句。决定表达式写在关键字之后,在括号之间,后跟要执行的语句。
该
Number.isNaN
函数是一个标准的JavaScript函数,true
只有在给出它的参数时才返回NaN
。当您为其指定一个不表示有效数字的字符串时,该Number
函数会返回NaN
。因此,条件转换为“除非theNumber
不是数字,否则执行此操作”。在此示例中
if
,括号后面的语句包含在大括号({
和})
中。大括号可用于将任意数量的语句分组到一个语句中,称为块。在这种情况下你也可以省略它们,因为它们只包含一个语句,但为了避免考虑是否需要它们,大多数JavaScript程序员在每个包装语句中都使用它们。除了偶尔的单行之外,我们大多数都遵循本书中的惯例。if (1 + 1 == 2) console.log("It's true"); // → It's true
您通常不仅拥有在条件成立时执行的代码,而且还有处理其他情况的代码。此备用路径由图中的第二个箭头表示。您可以与else关键字一起使用if,以创建两个单独的备用执行路径。
let theNumber = Number(prompt("Pick a number")); if (!Number.isNaN(theNumber)) { console.log("Your number is the square root of " + theNumber * theNumber); } else { console.log("Hey. Why didn't you give me a number?"); }
如果您有两个以上的路径可供选择,您可以将多个
if/ else
对“链接” 在一起。这是一个例子:let num = Number(prompt("Pick a number")); if (num < 10) { console.log("Small"); } else if (num < 100) { console.log("Medium"); } else { console.log("Large"); }
程序将首先检查是否
num
小于10.如果是,则选择该分支,显示"Small"
并完成。如果不是,它需要else
分支,它本身包含第二个分支if
。如果第二个条件(< 100
)成立,则表示该数字介于10和100之间,并"Medium"显示。如果没有,else
则选择第二个和最后一个分支。while 和 loop 循环
考虑一个程序,输出0到12之间的所有偶数。一种写这个的方法如下:
console.log(0); console.log(2); console.log(4); console.log(6); console.log(8); console.log(10); console.log(12);
这是有效的,但编写程序的想法是减少工作量,而不是更多。如果我们需要所有偶数小于1,000的数字,这种方法将是行不通的。我们需要的是一种多次运行一段代码的方法。这种形式的控制流称为循环。
循环控制流程允许我们回到我们之前的程序中的某个点,并使用当前的程序状态重复它。如果我们将它与一个重要的绑定结合起来,我们可以这样做:
let number = 0; while (number <= 12) { console.log(number); number = number + 2; } // → 0 // → 2 // … etcetera
以关键字开头的语句
while
会创建一个循环。这个词while
之后是括号中的表达式,然后是一个语句,就像if
。只要表达式生成一个true
在转换为布尔值时给出的值,循环就会继续输入该语句。该
number
结合证明绑定可以跟踪项目的进展方式。每次循环重复时,number
获得的值比前一个值多2。在每次重复开始时,将其与数字12进行比较,以确定程序的工作是否完成。作为一个实际上做了一些有用的事情的例子,我们现在可以编写一个程序来计算并显示2 10(2到10次幂)的值。我们使用两个绑定:一个用于跟踪我们的结果,另一个用于计算我们将此结果乘以2的频率。循环测试第二个绑定是否已达到10,如果没有,则更新两个绑定。
let result = 1; let counter = 0; while (counter < 10) { result = result * 2; counter = counter + 1; } console.log(result); // → 1024
计数器也可以启动1并检查<= 10,但由于第4章中显而易见的原因,最好习惯于从0开始计数。
甲do环类似于控制结构while回路。它只有一点不同:一个do循环总是至少执行一次它的主体,它开始测试它是否应该只在第一次执行后停止。为了反映这一点,测试出现在循环体之后。
let yourName; do { yourName = prompt("Who are you?"); } while (!yourName); console.log(yourName);
该程序将强制您输入名称。它会一次又一次地问,直到它得到一个不是空字符串的东西。应用!运算符会在取消它之前将值转换为布尔类型,并将除""转换为的所有字符串转换为true。这意味着循环继续循环,直到您提供非空名称。
缩进代码
在这些例子中,我一直在语句前添加空格,这些空格是一些较大语句的一部分。这些空间不是必需的 - 如果没有它们,计算机将接受该程序。事实上,即使程序中的换行也是可选的。如果您愿意,可以将程序编写为单行。
这个缩进在块内的作用是使代码的结构脱颖而出。在其他块中打开新块的代码中,很难看到一个块结束而另一个块开始的位置。通过适当的缩进,程序的视觉形状对应于其内部的块的形状。我喜欢为每个打开的块使用两个空格,但味道不同 - 有些人使用四个空格,有些人使用制表符。重要的是每个新块都会增加相同的空间。
if (false != true) { console.log("That makes sense."); if (1 < 2) { console.log("No surprise there."); } }
大多数代码编辑器程序(包括本书中的程序)将通过自动缩进新行的正确数量来提供帮助。
for循环
许多循环遵循while示例中显示的模式。首先创建“计数器”绑定以跟踪循环的进度。然后是一个while循环,通常带有一个测试表达式,用于检查计数器是否已达到其最终值。在循环体的末尾,计数器被更新以跟踪进度。
由于这种模式非常常见,因此JavaScript和类似语言提供了一种略短且更全面的形式,即for循环。
for (let number = 0; number <= 12; number = number + 2) { console.log(number); } // → 0 // → 2 // … etcetera
该程序完全等同于早期的偶数打印示例。唯一的变化是所有与循环“状态”相关的语句在之后被组合在一起for。
for关键字后面的括号必须包含两个分号。第一个分号之前的部分通常通过定义绑定来初始化循环。第二部分是检查循环是否必须继续的表达式。最后一部分在每次迭代后更新循环的状态。在大多数情况下,这比while构造更短更清晰。
这是使用而不是代码计算2^10的代码:
forwhile
let result = 1; for (let counter = 0; counter < 10; counter = counter + 1) { result = result * 2; } console.log(result); // → 1024
循环中断
产生循环条件false并不是循环完成的唯一方法。有一个特殊的声明break,它具有立即跳出封闭循环的效果。
该计划说明了该break声明。它找到第一个数字大于或等于20并且可以被7整除。
for (let current = 20; ; current = current + 1) { if (current % 7 == 0) { console.log(current); break; } } // → 21
使用remainder(%)运算符是一种简单的方法来测试一个数字是否可以被另一个数字整除。如果是,则其除法的余数为零。
for示例中的构造没有检查循环结束的部分。这意味着除非break执行内部语句,否则循环将永远不会停止。
如果您要删除该break语句或者您不小心写出了始终产生的结束条件true,那么您的程序将陷入无限循环。陷入无限循环的程序永远不会完成运行,这通常是一件坏事。
如果在这些页面上的一个示例中创建无限循环,通常会询问您是否要在几秒钟后停止脚本。如果失败,您将必须关闭您正在使用的选项卡,或者关闭整个浏览器的某些浏览器,以便恢复。
该continue关键字是类似break的,因为它影响了一个循环的进度。当continue在循环体中遇到时,控制跳出体外并继续循环的下一次迭代。
简洁地更新绑定
特别是在循环时,程序通常需要“更新”绑定以根据该绑定的先前值保存值。
counter = counter + 1 ;
JavaScript为此提供了一个快捷方式。
counter += 1;
类似的快捷方式适用于许多其他操作员,例如
result *= 2
加倍result或counter -= 1向下计数。这使我们可以更多地缩短我们的计数例子。
for (let number = 0; number <= 12; number += 2) { console.log(number); }
对于
counter += 1
和counter -= 1
,甚至更短的等价物:counter++
和counter--
。使用switch调度值
代码看起来像这样并不罕见:
if(x == “value1”)action1(); else if(x == “value2”)action2(); else if(x == “value3”)action3(); else defaultAction();
有一种被称为构造的构造switch旨在以更直接的方式表达这种“调度”。不幸的是,JavaScript用于此的语法(它继承自C / Java编程语言系列)有些尴尬 - 一系列if语句可能看起来更好。这是一个例子:
switch (prompt("What is the weather like?")) { case "rainy": console.log("Remember to bring an umbrella."); break; case "sunny": console.log("Dress lightly."); case "cloudy": console.log("Go outside."); break; default: console.log("Unknown weather type!"); break; }
您可以
case
在打开的块内放置任意数量的标签switch
。程序将在与给定值对应的标签处开始执行switch
,或者default
如果未找到匹配值则开始执行。它将继续执行,甚至跨越其他标签,直到它达到break
声明。在某些情况下,例如"sunny"示例中的情况,这可以用于在案例之间共享一些代码(它建议在晴天和多云天气外面)。但要小心 - 很容易忘记这样一个break
,这会导致程序执行你不想执行的代码。大写
绑定名称可能不包含空格,但使用多个单词来清楚地描述绑定所代表的内容通常很有帮助。这些几乎是您编写带有多个单词的绑定名称的选择:
fuzzylittleturtle fuzzy_little_turtle FuzzyLittleTurtle fuzzyLittleTurtle
第一种风格可能难以阅读。我更喜欢下划线的外观,虽然这种风格有点痛苦。标准的JavaScript函数和大多数JavaScript程序员都遵循底层样式 - 它们将除了第一个单词之外的每个单词都大写。习惯这样的小事并不难,具有混合命名风格的代码可能会让人难以理解,所以我们遵循这个惯例。
在少数情况下,例如Number函数,绑定的第一个字母也是大写的。这样做是为了将此函数标记为构造函数。在第6章中,构造函数将变得清晰。就目前而言,重要的是不要被这种明显缺乏一致性所困扰。
注释
通常,原始代码不会传达您希望程序向人类读者传达的所有信息,或者以一种人们可能无法理解的神秘方式传达它。在其他时候,您可能只想在计划中包含一些相关的想法。这就是评论的意思。
注释是一段文本,是程序的一部分,但完全被计算机忽略。JavaScript有两种写评论的方式。要编写单行注释,可以使用两个斜杠字符(
//
),然后使用注释文本。let accountBalance = calculateBalance(account); // It's a green hollow where a river sings accountBalance.adjust(); // Madly catching white tatters in the grass. let report = new Report(); // Where the sun on the proud mountain rings: addToReport(accountBalance, report); // It's a little valley, foaming like light in a glass.
一个//注释只能进入该行的末尾。无论是否包含换行符,都将在整个/和之间/忽略一段文本。这对于添加有关文件或程序块的信息块很有用。
/ * I first found this number scrawled on the back of an old notebook. Since then, it has often dropped by, showing up in phone numbers and the serial numbers of products that I've bought. It obviously likes me, so I've decided to keep it. */ const myNumber = 11213;
概要
您现在知道程序是由语句构建的,语句本身有时包含更多语句。语句倾向于包含表达式,这些表达式本身可以用较小的表达式构建。
将语句放在一起后会为您提供一个从上到下执行的程序。您可以通过使用条件(引入控制流的干扰
if
,else
和switch
)和循环(while
,do
,和for
)语句。绑定可用于在名称下存档数据,它们对于跟踪程序中的状态非常有用。环境是定义的绑定集。JavaScript系统总是在您的环境中添加许多有用的标准绑定。
函数是封装一段程序的特殊值。你可以通过写作来调用它们
functionName(argument1, argument2)
。这样的函数调用是表达式并且可以产生值。