JS —— 分支结构
本文按照 Mozilla 贡献者基于 CC-BY-SA 2.5 协议发布的以下文章改编:
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Control_flow_and_error_handling
- https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/conditionals
基础的数据结构,我们了解不少了。现在到了去用它们做点什么的时候了。
加速!这里我们要开始讲一个基础结构——分支结构。
开始之前
人类(以及其他的动物)无时无刻不在做决定,这些决定都影响着他们的生活,从小事(“我应该吃一片还是两片饼干”)到重要的大事(“我应该留在我的祖国,在我父亲的农场工作;还是应该去美国学习天体物理学”)。
条件语句结构允许我们来描述在 JS 中这样的选择,从不得不作出的选择(例如:“一片还是两片”)到产生的结果或这些选择(也许是“吃一片饼干”可能会“仍然感觉饿”,或者是“吃两片饼干”可能会“感觉饱了,但妈妈会因为我吃掉了所有的饼干而骂我”。)
布尔值
在正式开始分支结构之前,我们要讲下**布尔值(Boolean)**,这是进行条件判断的基础,分支和循环结构都要用到。
布尔值也是一种类型,和我们之前讲过的数字和字符串一样。不同的是它只有两个值: true
和 false
。分别对应 “真” 和 “假”。
1 | > true; |
一般情况下我们也不会这样用布尔值,而是采用一些比较运算和逻辑运算来得到,就像下面这样。
1 | > 1 === 1; //1 和 1 相等,正确,是 true |
比较运算
比较运算符是一个二元运算符,意思是左右两边分别需要一个运算数,就像之前我们讲过的四则运算一样。不过比较运算的结果不是普通的数字,而是一个布尔值,即 true
和 false
。
1 | > 1 + 2; // 四则运算,结果是数字 |
通过这里,我们可以很明显的看出,比较运算有这两个特点:
- 如果表达式成立,那么值为 true
- 如果表达式不成立,那么值为 false
和数学类似,我们有大于,小于,等于这些比较运算符。不同的是,我们的大于等于和小于等于是 >=
和 <=
,因为键盘打不出来数学里那个。而等于一般使用 ===
,这样不会和赋值 =
混淆。常用的比较运算符有这些:
运算符 | 描述 |
---|---|
等于 Equal (== ) |
如果两边操作数相等时返回true。 |
不等于 Not equal (!= ) |
如果两边操作数不相等时返回true |
全等 Strict equal (=== ) |
两边操作数相等且类型相同时返回true。 |
不全等 Strict not equal (!== ) |
两边操作数不相等或类型不同时返回true。 |
大于 Greater than (> ) |
左边的操作数大于右边的操作数返回true |
大于等于 Greater than or equal (>= ) |
左边的操作数大于或等于右边的操作数返回true |
小于 Less than (< ) |
左边的操作数小于右边的操作数返回true |
小于等于 Less than or equal (<= ) |
左边的操作数小于或等于右边的操作数返回true |
下面是一些示例,可以看到它们和预期的结果是符合的:
1 | > 1 + 2 >= 3; |
字符串也可以使用比较运算,它们之间的比较使用的是字典序,由于日常应用的时候基本只需要判断字符串之间是否相等,所以这里不详细提及字典序大小的比较。
1 | > "aaa" === 'aaa'; // 相等 |
你可能会注意到,上面我给出的示例只用了全等号(===
)和不全等号(!==
),而没有使用普通的等于和不等于。事实上我们提倡尽可能使用三个等号组成的全等号,因为它可以避免 JS 过于宽松的自动类型转换带来的一些容易造成错误的问题。看看下面的代码:
1 | > "0" == 0; // 自动类型转换 |
这里等于号和不等号遇到了一些问题:它把一个字符串和一个数字进行了转换,然后认为它们是相等的。这可能会导致你的程序出现一些难以排查的错误,所以尽可能避免它们。
逻辑运算
在进行比较运算的时候,我们可能会需要将多个条件连起来,我们假设有这三个变量需要判断:
num1
大于 0 ,小于 100 。num2
可以被 2 整除或者可以被 3 整除。num3
既不被 5 整除,又不被 4 整除。
这就需要使用逻辑运算,将多个表达式组合起来,然后得到想要的结果。逻辑运算符有三种:
运算符 | 描述 | 示例 |
---|---|---|
与 ( && ) |
当两边表达式值都为 true 时为 true,否则为 false | true && true === true true && false === false |
或 ( || ) |
当两边表达式值有一个为 true 时为 true,否则为 false | true || false === true false || flase === false |
非 ( ! ) |
当后面的值是 true 是为 false,反之为 true | !true === false !false === true |
因此上面我们提到的例子可以这么写:
1 | num1 > 0 && num1 < 100; |
前面两个应该比较好理解,第三个为什么要带个括号呢?答案是运算符优先级问题。括号的作用是让括号里面的东西先算,这个学过数学都知道,放在这里也是一样的。如果我们去掉这个括号,就相当于下面这样:
1 | !num3 % 5 === 0 || num3 % 4 === 0); |
可以看到是左边的那个非运算符先算,再到后边的或运算符,就出错了。运算符优先级是这样:先算 !
,再算 &&
,最后算 ||
,同级别从左到右。如果写程序的时候个别优先级不了解,那就多打几个括号,最多丑点。
if 语句
前面讲了这么多逻辑,现在有请我们的主角出场吧:就是 if 语句 !有了它,你就可以在程序里面根据不同的条件,去做出一系列的决定。以下是它的伪代码表示:
1 | if (一个表达式或者值) { |
关键字 if,并且后面跟随括号。
要测试的条件,放到括号里(通常是“这个值大于另一个值吗”或者“这个值存在吗”)。这个条件会利用比较运算符(我们会在最后的模块中讨论)进行比较,并且返回 true 或者 false 。
一组花括号,在里面我们有一些代码——可以是任何我们喜欢的代码,并且只会在条件语句返回 true 的时候运行。一般情况下前一个大括号和 if 在同一行,后一个单独一行,这样比较美观,也可以避免 JS 自动加入分号的机制造成的问题。
关键字 else 。
另一组花括号,在里面我们有一些代码——可以是任何我们喜欢的代码,并且当条件语句返回值不是 true 的话,它才会运行。
这段代码真的非常易懂——它说“如果(if)条件(condition)返回true,运行代码A,否则(else)运行代码B”
注意:你不一定需要else和第二个花括号——下面的代码也是符合语法规则的:
1 | if (一个表达式) { |
多个条件代码可以使用 else if
,你可以用它来进行多个条件的判断。
1 | if (一个表达式) { |
if 语句也可以嵌套:
1 | if (一个表达式) { |
有时候你可能会看到 if…else 语句没有写花括号,像下面的速记风格:
1 | if (一个表达式) 一行代码; |
这是完全有效的代码,但不建议这样使用——因为如果有花括号进行代码切割的话,整体代码被切割为多行代码,更易读和易用。
switch 语句
在需要多重分支时可以使用基于一个数字或字符串的 switch 语句:
1 | let x = 1; |
因为 switch 语句本质是跳到指定的标签开始执行,所以每个 case 结尾需要加上 break
语句用来退出,否则它会一直按顺序执行,运行到后面的代码,比如这样。
1 | let x = 1; |
输出就变成了
1 | 1 |
一些例子
现在已经到了比较复杂的程序,所以我们就不再直接使用交互式解释器来运行我们的代码了。接下来的所有例子都会是一个单独的 JS 文件,你可以加到 HTML 里面通过浏览器运行,然后在控制台查看输出。
假设有一个年份,变量名叫做 year,现在要你判断它是否是闰年。
1582年以来的置闰规则:
- 普通闰年:公历年份是4的倍数,且不是100的倍数的,为闰年(如2004年、2020年等就是闰年)。
- 世纪闰年:公历年份是整百数的,必须是400的倍数才是闰年(如1900年不是闰年,2000年是闰年)。
写成程序就是这样
1 | // 实际情况需要配合输入,这个以后在讲 DOM 的时候会了解 |
当然你也可以按照别的顺序进行判断。就像这个例子。只要答案是对的就可以。
1 | if (year % 4 === 0) { |
给定一个数组,然后从小到大排序数组里面的数字并输出,用空格分隔:
1 | let myArray = [3, 1, 2]; |
这里写成这样是为了演示分支,下章循环结构我们会有更简便的方法。实际上 JS 的数组还有一个自带的方法 .sort()
,可以从小到大排序数组里面的内容。
1 | let myArray = [3, 1, 2]; |
码风
貌似我们之前的 if 语句都是这样的:
1 | if () { |
其实 if 还可以写而成这样
1 | if () |
我们可以看到这里的开头大括号放在了下一行。其实其他的语言有很多也是这么写的。但是 JS 里面不推荐这么写,因为 JS 有个自动插入分号的机制,这本来是为了你万一忘记打分号也能正常运行程序用的,但是有时候会带来一点不愉快的事情:
1 | return |
看起来你是想要返回一个对象,然而这种机制导致它实际上是这样的意思:
1 | return; // BAD!!! |
你可以看到它直接返回了,而后面那个对象就没有了。所以还是要这么写:
1 | return { |
为了统一风格,我们就把所有的开头大括号不换行了。
练习
还记得 Tux 么?之前它让你写了一个计算 BMI 的程序,现在它要你去把这个程序改一下,判断 Tux 的 BMI 是不是正常的。
下面是程序的开头,你要用给出的体重 (变量 weight
) 和身高 (变量 height
) 来计算 BMI,同时判断 BMI 水平,输出“偏瘦”(小于18.5) “正常” (在 18.5-23.9 之间)和 “偏胖” (大于 23.9)。(BMI=体重/身高^2)
1 | let weight = 82; |