控制流:入门指南
switch语句
循环
打破循环
持续循环
概括
总有那么一些时候,你需要依靠自动化任务来运行代码。想想交通信号灯,它们控制着交通,免去了城市在每个十字路口都安排交警的麻烦。或者想想流水线,它以惊人的速度执行着繁琐的任务。
同样,条件语句和循环语句使我们能够编写高效的代码。之所以使用“控制流”这个术语,是因为解释器是从上到下逐步读取代码的。
当你写出一堆类似这样的语句时:
let firstVar = 'dummy';
let secondVar = 'bozo';
let thirdVar = 'stoog';
console.log(firstVar);
console.log(secondVar);
console.log(thirdVar);
解释器从上到下读取代码并按顺序执行。如果我们不考虑用户会与我们的应用交互,这倒没什么问题。但与机器人不同,人类与应用的交互方式可能并非我们所期望的,因此我们必须通过设置条件来应对这种情况。
把条件语句想象成路上的岔路口。你可能已经熟悉其中一个条件语句——if 语句。让我们尝试在用户与我们的应用交互的场景中使用它。
如果/否则
假设我们受命为一家名为“宠物天堂”(Pet Nirvana)的宠物日托中心设计一个提交表单。首席执行官拉里·戴维斯想问潜在客户的问题之一是:“您有多少只宠物?”
var answer = prompt("how many pets do you have?");
alert(answer);
专业提示:该
prompt方法接收用户响应并返回用户的输入。我们可以用它来测试用户输入。以前,该alert方法曾用于测试输出,但console.log()现在已经取代了这一角色。
我们通常会假设用户会输入一个数字,但如果有人想输入一长串字符串来戏弄我们呢?
如果没有控制流,恶意用户可能会这样做:DROP/*you got jacked!*/users
这短短一行 SQL 代码就能删除数据库中的所有用户。这只是一个例子。实际上,只需要一个简单的字符串就能让我们的应用程序崩溃。
想象一下,如果我们想计算每位主人平均拥有的宠物数量,以便戴维斯先生知道他应该在宠物用品上花费多少钱。
不用担心函数部分。将下面的代码复制到编辑器中,然后尝试输入一个数字。
var pets = 35;
var owners = 15;
var petsPerOwner = average(pets, owners);
//======Pet Info Form
var answer = prompt("how many pets do you have?");
//============
updateAvg(answer) // update based on answer, add new owner
console.log(`There are now ${petsPerOwner} pets per owner at Pet Nirvana `)
//============
//Functions are hoisted up in JavaScript.
//We'll deal with 'em later
function average(total, number){
return total / number;
}
function updateAvg(newNum){
pets += Number(newNum); // register new pet(s)
owners += 1 // register new owner
petsPerOwner = Math.ceil(average(pets, owners)); // find new average, round up
}
你应该得到一个比较接近整数的平均值。现在,尝试在提示符中插入一个随机字符串。
你应该看到“目前 Pet Nirvana 每位主人拥有的宠物数量为 NaN”。
专业提示:具体错误源于 `Number()` 函数。该函数会将字符串类型的数字强制转换
'3'为数字类型。我们接收到的用户输入通常都是字符串,因此在需要数字时,`Number
( )` 函数非常有用。但如果输入是普通的单词字符串,而我们试图将其强制转换为数字,则会得到错误。3Number()NaN
这看起来或许没什么大不了,但在现实世界中却是一场灾难。仅仅因为无法筛选数据,我们就失去了获取重要信息的途径。
我们必须对我们想要处理的数据拥有控制权。
如果/否则
幸好我们有 if/else 语句。
var answer = prompt("how many pets do you have?");
if(isNaN(answer)){
alert("Error: input a number");
}else{
updateAvg(answer) // update based on answer, add new owner
console.log(`There are now ${petsPerOwner} pets per owner at Pet Nirvana `)
}
我们不再随意接收任何响应,而是通过检查答案是否为数字来控制数据流。还记得我们之前的NaN错误吗?当你尝试对字符串执行不兼容的算术运算符时就会出现这个错误。如果条件为真,if 语句内的任何代码块都会自动执行。
注意:无需编写,
isNaN(answer) == true因为 if 语句可以判断一个值的真假。
"hello" / 4; //> NaN
有一个内置函数可以isNaN()检查数据类型是否为数字。如果数据类型不是数字,则返回 true;否则,返回 false。
为了更好地理解,让我们把刚才写的代码转换成伪代码。
/*
If the answer is not a number
output an error
Else(otherwise)
update the average
*/
短路
还有另一种控制数据流的方法。我们可以绕过“或”运算符。
isNaN(answer) || (petsPerOwner = updateAvg(answer));
console.log(`There are now ${petsPerOwner} pets per owner at Pet Nirvana `);
OR 运算符会查找第一个真值。找到真值后,它会跳出条件判断。因此,如果答案不是数字,我们就无需更新平均值。
问题在于它answer仍然保留着不需要的值,限制了我们后续对该变量的操作。你还会注意到,我们无法向用户提供任何反馈。虽然短路 OR 运算符是个巧妙的技巧,但它并非控制数据流的最佳方法。
否则如果
如果我们想检查两个以上的可能性呢?如果宠物天堂的首席执行官还想提醒宠物主人,目前公司每位主人最多只能饲养三只宠物呢?这样一来,我们不仅需要检查用户输入的数据类型,还需要提醒饲养超过四只宠物的主人注意宠物数量限制。
使用 else if 语句会很有用。你可以根据需要将多个 else if 语句串联起来。
if(/*first condition*/){
}else if(/*second condition*/){
}else if(/*third condition*/){
}
为什么我们不先尝试用伪代码来编写解决方案,然后再开始编写代码呢?
/*
If the answer is not a number
output an error
Else if the answer is greater than three
warn the user that they have too many pets
Else(otherwise)
update the average
*/
让我们在代码中测试一下。当你输入一个大于 3 的数字时,应该会收到警告。
var answer = prompt("how many pets do you have?");
if(isNaN(answer)){
alert("Error: input a number");
}else if(Number(answer) > 3){
alert("Sorry, we currently only accept 3 pets");
}
else{
updateAvg(answer) // update based on answer, add new owner
console.log(`There are now ${petsPerOwner} pets per owner at Pet Nirvana `)
}
任务
哦,哦。你和你的客户之间沟通出了问题。显然,他希望即使宠物主人的宠物总数超过限制,平均宠物数量也应该更新,但他想在更新之前先询问用户是否同意这个限制。
已为您提供伪代码。
/*
Else if the answer is greater than three
Prompt the user and ask if they're ok with the limit
If the prompt equals yes
update the average
*/
switch语句
在使用 if 语句的过程中,你可能会遇到以下类型的代码:
if (x == "case 1") runThis();
else if (x == "case 2") runThat();
else if (x == "case 3") runThis();
else if (x == "case 4") runThat();
如果要处理这么多案例,使用名为“控制流结构”的工具可能会更好switch。
一个基本的 switch 语句以初始值开始,然后提供带有可选默认值的 case 块。
case语句其实就是更易于阅读的if语句。
let greeting = 'hello'
switch(greeting){
case 'hello': // is the same as if(greeting === 'hello')
//code goes here
//break
default: // is the same as else
}
这里有一个更详细的例子供你参考。
let number = 2;
switch(number) {
case 1:
console.log("this is one");
break;
case 2:
console.log("this is two");
break;
case 3:
console.log("this is three");
break;
default:
console.log("I can't count past three.");
}
//can you guess what the result will be?
break 关键字至关重要。如果省略它们,即使条件满足,switch 语句也会继续执行,自动执行下一个 case 代码块,直到遇到 break 关键字或所有 case 代码块都执行完毕为止。
所以,如果我们省略了“ breakin” case 2:,就会得到:
"this is two"
"this is three"
把 switch 语句想象成一条管道。break 语句就像堤坝,防止管道泄漏到其他部分。
关于 switch 语句,还有一点需要注意,那就是它可以对 case 进行分组。让我们扩展一下问候示例,来展示我们的 case 链。
switch(prompt('greet me!')){
case 'hello':
case 'hi':
case 'yo':
console.log("Hey? What's up?");
break;
default:
console.log("I don't speak your lingo.");
}
循环
现在我们知道如何控制输入的数据,但是如何控制发送给用户的数据呢?
戴维斯先生现在想为他的经纪人添加一个评分系统。他希望在他们的个人资料名称下方显示星级。
我们可以手动渲染所有星星……
//you can see that Becky has accumulated a rounded average of four stars
var becky = {name:'Becky Star', stars: 4}
//====Profile
//Mock profile name
console.log(becky.name)
//we can render our stars four times
render() + render() + render() + render();
//====
//Dummy render function
function render(){
return '*';
}
while 循环
或者我们可以使用while循环。while 循环会检查条件是否为真,如果为真,则会一直执行代码块,直到条件为假为止。请确保你的循环最终能够产生假值。否则,你就会陷入无限循环。
// you usually have to set a counter and either decrement or increment it till it satisfies the condition.
counter = 4;
while(counter != 0){
console.log(counter);
--counter //we decrease the counter by 1
}
激发你的创造力。使用 while 循环渲染一行四颗星。输出结果应如下所示:'****'
提示:计数器编号和星星数量都很重要。
提示:加号等于运算符会很有用。
当
do while 循环与 while 循环类似,区别在于 do while 循环保证代码块在第一次循环时就会执行。
这就像在说,“一定要先执行这个(这段代码块)”。现在,当我的条件为真时,继续执行该代码块中的内容。
让我们重新审视一下宠物编号提示,并使用 do while 循环重写它。
let answer;
do {
answer = prompt("how many pets do you have?");
}while(isNaN(answer))
如果用户不输入数字,这段代码会不断提示用户输入信息。
让我们在循环中添加一个条件,以加强我们对信息的控制。
let answer;
do {
answer = prompt("how many pets do you have?");
if(isNaN(answer)){
alert("error: enter a number");
}
}while(isNaN(answer))
现在我们已经创建了一个反馈循环,可以提醒用户他们的错误,并允许他们立即纠正错误。
for 循环
简单来说,for 循环就是一个带有“电池”的 while 循环。你知道你需要在循环外部设置一个计数器,然后确保它不断递减或递增吗?
使用 for 循环,您可以将所有内容都设置到一个参数中()。
/* first you set the counter*/
//var x = 4;
/* then you set the condition*/
//x != 0;
/*finally, you decrement or increment
depending on your condition
*/
//--x
//Now let's install the batteries
for(var x = 4; x!= 0; --x){
//we're ready to loop
}
还记得你之前要做的渲染任务吗?这里是用 for 循环的解决方案。
//we can see here that Becky has accumulated a rounded total of four stars
var becky = {name:'Becky Star', stars: 4}
var starRow = '';
//====Profile
//Mock profile name
console.log(becky.name)
//rendering with the for loop
for(cnt = becky.stars; cnt != 0; --cnt){
starRow += render();
}
starRow; // > '****'
//Dummy render function
function render(){
return '*'
}
打破循环
循环会一直运行,直到条件为假。有时我们可能想要像电影《盗梦空间》那样,使用关键字跳出循环break。
//this is a potential infinite loop
while(true){
console.log("I'm free!");
break; // phew
}
你可能会遇到需要使用嵌套 for 循环的问题。
var matrix = [[1,2,3],[4,5,6],[7,8,9]];
//prints 1,2,3,4...
for(var outer=0;outer < matrix.length; ++outer){
for(var inner=0;inner < matrix.length; ++inner){
console.log(matrix[outer][inner])
}
}
在内层 for 循环中编写 break 语句会中断内层循环,但外层循环会继续运行。
var matrix = [[1,2,3],[4,5,6],[7,8,9]];
//prints 1,2,3,4...
for(var outer=0;outer < matrix.length; ++outer){
for(var inner=0;inner < matrix.length; ++inner){
if(matrix[outer][inner] === 2){
break;
}
}
}
如果你想彻底摆脱所有循环,你需要给循环添加标签。在 for 循环名前加上你想要的任何名称,后面跟一个冒号。然后,当你准备跳出循环时,在 break 关键字后面加上你的标签名称。
labelName: for(){
for(){
break labelName;
}
}
这是我们修改后的嵌套循环。
var matrix = [[1,2,3],[4,5,6],[7,8,9]];
//the for loop can start on a newline
outer:
for(var outer=0;outer < matrix.length; ++outer){
for(var inner=0;inner < matrix.length; ++inner){
if(matrix[outer][inner] === 2){
break outer;
}
}
}
持续循环
continue 指令允许我们跳过循环中的某些步骤。或许这个指令应该叫做 skip,但没办法,我们姑且就叫 continue 吧。
for (let i = 0; i < 10; i++) {
// if i is even, skip the rest of the code.
if (i % 2 == 0) continue;
console.log(i); // 1, 3, 5, 7, 9
}
专业提示:如果代码块可以用一行写完,则不需要花括号。
概括
我们攻克了if/else if/else语句,解决了switch语句问题,并理清了 `if`、`if`while和do while`if` 之间的关系for loops。我们还学习了如何跳出循环以及如何继续循环。接下来,我们将学习 JavaScript 程序如何围绕函数展开。
