JavaScript基础语法
# 3.1 类型转换
大多数情况下,运算符和函数会自动将赋予它们的值转换为正确的类型。
比如,alert
会自动将任何值都转换为字符串以进行显示。算术运算符会将值转换为数字
# 3.1.1 字符串转换
alert(value)
将 value
转换为字符串类型,然后显示这个值
我们也可以显式地调用 String(value)
来将 value
转换为字符串类型
let value = true;
alert(typeof value); // boolean
value = String(value); // 现在,值是一个字符串形式的 "true"
alert(typeof value); // string
2
3
4
# 3.1.2 数字型转换
在算术函数和表达式中,会自动进行 number 类型转换
alert( "6" / "2" ); // 3, string 类型的值被自动转换成 number 类型后进行计算
我们也可以使用 Number(value)
显式地将这个 value
转换为 number 类型
let str = "123";
alert(typeof str); // string
let num = Number(str); // 变成 number 类型 123
alert(typeof num); // number
2
3
4
number 类型转换规则:
值 | 变成…… |
---|---|
undefined | NaN |
null | 0 |
true 和 false | 1 and 0 |
string | 去掉首尾空格后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 0 。否则,将会从剩余字符串中“读取”数字。当类型转换出现 error 时返回 NaN 。 |
alert( Number(" 123 ") ); // 123
alert( Number("123z") ); // NaN(从字符串“读取”数字,读到 "z" 时出现错误)
alert( Number(true) ); // 1
alert( Number(false) ); // 0
2
3
4
注意
请注意 null
和 undefined
在这有点不同:null
变成数字 0
,undefined
变成 NaN
# 3.1.3 布尔型转换
它发生在逻辑运算中(稍后我们将进行条件判断和其他类似的东西),但是也可以通过调用 Boolean(value) 显式地进行转换
值 | 变成…… |
---|---|
0 , null , undefined , NaN , "" | false |
其他值 | true |
alert( Boolean(1) ); // true
alert( Boolean(0) ); // false
alert( Boolean("hello") ); // true
alert( Boolean("") ); // false
2
3
4
注意
包含 0 的字符串 "0"
是 true
# 3.1.4 总结
有三种常用的类型转换:转换为 string 类型、转换为 number 类型和转换为 boolean 类型。
字符串转换 —— 转换发生在输出内容的时候,也可以通过 String(value)
进行显式转换。原始类型值的 string 类型转换通常是很明显的。
数字型转换 —— 转换发生在进行算术操作时,也可以通过 Number(value)
进行显式转换
布尔型转换 —— 转换发生在进行逻辑操作时,也可以通过 Boolean(value)
进行显式转换
上述的大多数规则都容易理解和记忆。人们通常会犯错误的值得注意的例子有以下几个:
- 对
undefined
进行数字型转换时,输出结果为NaN
,而非0
。 - 对
"0"
和只有空格的字符串(比如:" "
)进行布尔型转换时,输出结果为true
# 3.2 基础运算符
# 3.2.1 运算符相关术语
# 运算元
运算符应用的对象。比如说乘法运算 5 * 2
,有两个运算元:左运算元 5
和右运算元 2
。有时候人们也称其为“参数”而不是“运算元”
# 一元运算符
如果一个运算符对应的只有一个运算元,那么它是 一元运算符
let x = 1;
x = -x;
alert( x ); // -1,一元负号运算符生效
2
3
# 二元运算符
如果一个运算符拥有两个运算元,那么它是 二元运算符
let x = 1, y = 3;
alert( y - x ); // 2,二元运算符减号做减运算
2
# 3.2.2 数学运算符
支持以下数学运算:
- 加法
+
, - 减法
-
, - 乘法
*
, - 除法
/
, - 取余
%
, - 求幂
**
.
# 取余 %
a % b
的结果是 a
整除 b
的 余数
alert( 5 % 2 ); // 1,5 除以 2 的余数
alert( 8 % 3 ); // 2,8 除以 3 的余数
2
# 求幂 **
求幂运算 a ** b
将 a
提升至 a
的 b
次幂
alert( 2 ** 2 ); // 2² = 4
alert( 2 ** 3 ); // 2³ = 8
alert( 2 ** 4 ); // 2⁴ = 16
2
3
# 3.2.3 用二元运算符 + 连接字符串
通常,加号 +
用于求和。
但是如果加号 +
被应用于字符串,它将合并(连接)各个字符串:
let s = "my" + "string";
alert(s); // mystring
2
注意:只要任意一个运算元是字符串,那么另一个运算元也将被转化为字符串。
举个例子:
alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"
2
alert(2 + 2 + '1' ); // "41",不是 "221"
在这里,运算符是按顺序工作。第一个 +
将两个数字相加,所以返回 4
,然后下一个 +
将字符串 1
加入其中,所以就是 4 + '1' = '41'
。
alert('1' + 2 + 2); // "122",不是 "14"
这里,第一个操作数是一个字符串,所以编译器将其他两个操作数也视为了字符串。2
被与 '1'
连接到了一起,也就是像 '1' + 2 = "12"
然后 "12" + 2 = "122"
这样。
二元 +
是唯一一个以这种方式支持字符串的运算符。其他算术运算符只对数字起作用,并且总是将其运算元转换为数字。
下面是减法和除法运算的示例:
alert( 6 - '2' ); // 4,将 '2' 转换为数字
alert( '6' / '2' ); // 3,将两个运算元都转换为数字
2
# 3.2.4 数字转化,一元运算符 +
加号 +
有两种形式。一种是上面我们刚刚讨论的二元运算符,还有一种是一元运算符。
一元运算符加号,或者说,加号 +
应用于单个值,对数字没有任何作用。但是如果运算元不是数字,加号 +
则会将其转化为数字。
例如:
// 对数字无效
let x = 1;
alert( +x ); // 1
let y = -2;
alert( +y ); // -2
// 转化非数字
alert( +true ); // 1
alert( +"" ); // 0
2
3
4
5
6
7
8
它的效果和 Number(...)
相同,但是更加简短
# 3.2.5 运算符优先级
这是一个摘抄自 Mozilla 的 优先级表 (opens new window)(你没有必要把这全记住,但要记住一元运算符优先级高于二元运算符):
优先级 | 名称 | 符号 |
---|---|---|
... | ... | ... |
15 | 一元加号 | + |
15 | 一元负号 | - |
14 | 求幂 | ** |
13 | 乘号 | * |
13 | 除号 | / |
12 | 加号 | + |
12 | 减号 | - |
... | ... | ... |
2 | 赋值符 | = |
... | ... | ... |
我们可以看到,“一元加号运算符”的优先级是 15
,高于“二元加号运算符”的优先级 12
。这也是为什么表达式 "+apples + +oranges"
中的一元加号先生效,然后才是二元加法
# 3.2.6 赋值运算符
赋值符号 =
也是一个运算符。从优先级表中可以看到它的优先级非常低,只有 2
。
所以当我们赋值时,比如 x = 2 * 2 + 1
,所有的计算先执行,然后 =
才执行,将计算结果存储到 x
# 赋值 = 返回一个值
在 JavaScript 中,所有运算符都会返回一个值。这对于 +
和 -
来说是显而易见的,但对于 =
来说也是如此
语句 x = value
将值 value
写入 x
然后返回 value。
下面是一个在复杂语句中使用赋值的例子:
let a = 1;
let b = 2;
let c = 3 - (a = b + 1);
alert( a ); // 3
alert( c ); // 0
2
3
4
5
上面这个例子,(a = b + 1)
的结果是赋给 a
的值(也就是 3
)。然后该值被用于进一步的运算
# 链式赋值(Chaining assignments)
let a, b, c;
a = b = c = 2 + 2;
alert( a ); // 4
alert( b ); // 4
alert( c ); // 4
2
3
4
5
链式赋值从右到左进行计算。首先,对最右边的表达式 2 + 2
求值,然后将其赋给左边的变量:c
、b
和 a
。最后,所有的变量共享一个值
# 3.2.7 原地修改
我们经常需要对一个变量做运算,并将新的结果存储在同一个变量中
可以使用运算符 +=
和 *=
来缩写这种表示,所有算术和位运算符都有简短的“修改并赋值”运算符
let n = 2;
n += 5; // 现在 n = 7(等同于 n = n + 5)
n *= 2; // 现在 n = 14(等同于 n = n * 2)
alert( n ); // 14
2
3
4
这类运算符的优先级与普通赋值运算符的优先级相同,所以它们在大多数其他运算之后执行:
let n = 2;
n *= 3 + 5;
alert( n ); // 16 (右边部分先被计算,等同于 n *= 8)
2
3
# 3.2.8 自增/自减
# 自增++
将变量与 1 相加
let counter = 2;
counter++; // 和 counter = counter + 1 效果一样,但是更洁
alert( counter ); // 3
2
3
# 自减--
let counter = 2;
counter--; // 和 counter = counter - 1 效果一样,但是更洁
alert( counter ); // 1
2
3
只用于变量
自增/自减只能应用于变量。试一下,将其应用于数值(比如 5++
)则会报错
# 前置与后置
运算符 ++
和 --
可以置于变量前,也可以置于变量后。
- 当运算符置于变量后,被称为“后置形式”:
counter++
。 - 当运算符置于变量前,被称为“前置形式”:
++counter
。
let counter = 1;
let a = ++counter; // (*)
alert(a); // 2
2
3
(*)
所在的行是前置形式 ++counter
,对 counter
做自增运算,返回的是新的值 2
。因此 alert
显示的是 2
。
下面让我们看看后置形式:
let counter = 1;
let a = counter++; // (*) 将 ++counter 改为 counter++
alert(a); // 1
2
3
(*)
所在的行是后置形式 counter++
,它同样对 counter
做加法,但是返回的是 旧值(做加法之前的值)。因此 alert
显示的是 1
。
自增/自减和其它运算符的对比
++/--
运算符同样可以在表达式内部使用。它们的优先级比绝大部分的算数运算符要高。
举个例子:
let counter = 1;
alert( 2 * ++counter ); // 4
2
# 3.2.9 位运算符
下面是位运算符:
- 按位与 (
&
) - 按位或 (
|
) - 按位异或 (
^
) - 按位非 (
~
) - 左移 (
<<
) - 右移 (
>>
) - 无符号右移 (
>>>
)
# 3.2.10 逗号运算符
逗号运算符能让我们处理多个语句,使用 ,
将它们分开。每个语句都运行了,但是只有最后的语句的结果会被返回
let a = (1 + 2, 3 + 4);
alert( a ); // 7(3 + 4 的结果)
2
这里,第一个语句 1 + 2
运行了,但是它的结果被丢弃了。随后计算 3 + 4
,并且该计算结果被返回
逗号运算符的优先级非常低
请注意逗号运算符的优先级非常低,比 =
还要低,因此上面你的例子中圆括号非常重要。
如果没有圆括号:a = 1 + 2, 3 + 4
会先执行 +
,将数值相加得到 a = 3, 7
,然后赋值运算符 =
执行 a = 3
,然后逗号之后的数值 7
不会再执行,它被忽略掉了。相当于 (a = 1 + 2), 3 + 4
。
# 3.3 比较运算符
- 大于 / 小于:
a > b
,a < b
。 - 大于等于 / 小于等于:
a >= b
,a <= b
。 - 检查两个值的相等:
a == b
,请注意双等号==
表示相等性检查,而单等号a = b
表示赋值。 - 检查两个值不相等:不相等在数学中的符号是
≠
,但在 JavaScript 中写成a != b
。
# 3.3.1 比较运算符结果为Boolean 类型
所有比较运算符均返回布尔值:
true
—— 表示“yes(是)”,“correct(正确)”或“the truth(真)”。false
—— 表示“no(否)”,“wrong(错误)”或“not the truth(非真)”。
和其他类型的值一样,比较的结果可以被赋值给任意变量
let result = 5 > 4; // 把比较的结果赋值给 result
alert( result ); // true
2
# 3.3.2 字符串比较
在比较字符串的大小时,JavaScript 会使用“字典(dictionary)”或“词典(lexicographical)”顺序进行判定,字符串是按字符(母)逐个进行比较的
alert( 'Z' > 'A' ); // true
alert( 'Glow' > 'Glee' ); // true
alert( 'Bee' > 'Be' ); // true
2
3
字符串的比较算法非常简单:
- 首先比较两个字符串的首位字符大小。
- 如果一方字符较大(或较小),则该字符串大于(或小于)另一个字符串。算法结束。
- 否则,如果两个字符串的首位字符相等,则继续取出两个字符串各自的后一位字符进行比较。
- 重复上述步骤进行比较,直到比较完成某字符串的所有字符为止。
- 如果两个字符串的字符同时用完,那么则判定它们相等,否则未结束(还有未比较的字符)的字符串更大
非真正的字典顺序,而是 Unicode 编码顺序
在上面的算法中,比较大小的逻辑与字典或电话簿中的排序很像,但也不完全相同。
比如说,字符串比较对字母大小写是敏感的。大写的 "A"
并不等于小写的 "a"
。哪一个更大呢?实际上小写的 "a"
更大。这是因为在 JavaScript 使用的内部编码表中(Unicode),小写字母的字符索引值更大
# 3.3.3 不同类型间的比较
当对不同类型的值进行比较时,JavaScript 会首先将其转化为数字(number)再判定大小
alert( '2' > 1 ); // true,字符串 '2' 会被转化为数字 2
alert( '01' == 1 ); // true,字符串 '01' 会被转化为数字 1
alert( true == 1 ); // true
alert( false == 0 ); // true
2
3
4
一个有趣的现象
有时候,以下两种情况会同时发生:
- 若直接比较两个值,其结果是相等的。
- 若把两个值转为布尔值,它们可能得出完全相反的结果,即一个是
true
,一个是false
。 例如:
let a = 0;
alert( Boolean(a) ); // false
let b = "0";
alert( Boolean(b) ); // true
alert(a == b); // true!
2
3
4
5
对于 JavaScript 而言,这种现象其实挺正常的。因为 JavaScript 会把待比较的值转化为数字后再做比较(因此 "0"
变成了 0
)。若只是将一个变量转化为 Boolean
值,则会使用其他的类型转换规则。
# 3.3.4 严格相等
普通的相等性检查 ==
存在一个问题,它不能区分出 0
和 false
,也同样无法区分空字符串和 false
这是因为在比较不同类型的值时,处于相等判断符号 ==
两侧的值会先被转化为数字。空字符串和 false
也是如此,转化后它们都为数字 0
严格相等运算符 ===
在进行比较时不会做任何的类型转换。
alert( 0 === false ); // false,因为被比较值的数据类型不同
同样的,与“不相等”符号 !=
类似,“严格不相等”表示为 !==
# 3.3.5 对 null 和 undefined 进行比较
当使用严格相等 ===
比较二者时
: 它们不相等,因为它们属于不同的类型。
```js run
alert( null === undefined ); // false
```
当使用非严格相等 ==
比较二者时
: JavaScript 存在一个特殊的规则,会判定它们相等。它们俩就像“一对恋人”,仅仅等于对方而不等于其他任何的值(只在非严格相等下成立)。
```js run
alert( null == undefined ); // true
```
当使用数学式或其他比较方法 < > <= >=
时:
: null/undefined
会被转化为数字:null
被转化为 0
,undefined
被转化为 NaN
。
# null vs 0
alert( null > 0 ); // (1) false
alert( null == 0 ); // (2) false
alert( null >= 0 ); // (3) true
2
3
相等性检查 ==
和普通比较符 > < >= <=
的代码逻辑是相互独立的
进行值的比较时,null
会被转化为数字,因此它被转化为了 0
。这就是为什么(3)中 null >= 0
返回值是 true,(1)中 null > 0
返回值是 false。
undefined
和 null
在相等性检查 ==
中不会进行任何的类型转换,它们有自己独立的比较规则,所以除了它们之间互等外,不会等于任何其他的值。这就解释了为什么(2)中 null == 0
会返回 false。
# undefined
alert( undefined > 0 ); // false (1)
alert( undefined < 0 ); // false (2)
alert( undefined == 0 ); // false (3)
2
3
(1)
和 (2)
都返回 false
是因为 undefined
在比较中被转换为了 NaN
,而 NaN
是一个特殊的数值型值,它与任何值进行比较都会返回 false
(3)
返回 false
是因为这是一个相等性检查,而 undefined
只与 null
相等,不会与其他值相等
# 3.4 条件分支
# 3.4.1 "if" 语句
if(...)
语句计算括号里的条件表达式,如果计算结果是 true
,就会执行对应的代码块
if (year == 2015) {
alert( "That's correct!" );
alert( "You're so smart!" );
}
2
3
4
# 3.4.2 布尔转换
if (…)
语句会计算圆括号内的表达式,并将计算结果转换为布尔型
# 3.4.3 "else" 语句
if
语句有时会包含一个可选的 "else" 块。如果判断条件不成立,就会执行它内部的代码
# 3.4.4 多个条件:"else if"
有时我们需要测试一个条件的几个变体。我们可以通过使用 else if
子句实现
let year = prompt('In which year was ECMAScript-2015 specification published?', '');
if (year < 2015) {
alert( 'Too early...' );
} else if (year > 2015) {
alert( 'Too late' );
} else {
alert( 'Exactly!' );
}
2
3
4
5
6
7
8
可以有更多的 else if
块。结尾的 else
是可选的
# 3.4.5 条件运算符 '?'
问号 ?
有时它被称为三元运算符,被称为“三元”是因为该运算符中有三个操作数
let result = condition ? value1 : value2;
计算条件结果,如果结果为真,则返回 value1
,否则返回 value2
# 3.4.6 多个 '?'
使用一系列问号 ?
运算符可以返回一个取决于多个条件的值
let age = prompt('age?', 18);
let message = (age < 3) ? 'Hi, baby!' :
(age < 18) ? 'Hello!' :
(age < 100) ? 'Greetings!' :
'What an unusual age!';
alert( message );
2
3
4
5
6
- 第一个问号检查
age < 3
。 - 如果为真 — 返回
'Hi, baby!'
。否则,会继续执行冒号":"
后的表达式,检查age < 18
。 - 如果为真 — 返回
'Hello!'
。否则,会继续执行下一个冒号":"
后的表达式,检查age < 100
。 - 如果为真 — 返回
'Greetings!'
。否则,会继续执行最后一个冒号":"
后面的表达式,返回'What an unusual age!'
# 3.4.6 '?' 的非常规使用
有时可以使用问号 ?
来代替 if
语句
let company = prompt('Which company created JavaScript?', '');
(company == 'Netscape') ?
alert('Right!') : alert('Wrong.');
2
3
根据条件 company =='Netscape'
,要么执行 ?
后面的第一个表达式并显示对应内容,要么执行第二个表达式并显示对应内容
不建议这样使用问号运算符。
# 3.5 逻辑运算符
JavaScript 中有四个逻辑运算符:||
(或),&&
(与),!
(非),??
(空值合并运算符)
虽然它们被称为“逻辑”运算符,但这些运算符却可以被应用于任意类型的值,而不仅仅是布尔值。它们的结果也同样可以是任意类型
# 3.5.1 ||(或)
两个竖线符号表示“或”运算符:
result = a || b;
alert( true || true ); // true
alert( false || true ); // true
alert( true || false ); // true
alert( false || false ); // false
2
3
4
如果操作数不是布尔值,那么它将会被转化为布尔值来参与运算
# 或运算寻找第一个真值
给定多个参与或运算的值:
result = value1 || value2 || value3;
或运算符 ||
做了如下的事情:
- 从左到右依次计算操作数。
- 处理每一个操作数时,都将其转化为布尔值。如果结果是
true
,就停止计算,返回这个操作数的初始值。 - 如果所有的操作数都被计算过(也就是,转换结果都是
false
),则返回最后一个操作数。
返回的值是操作数的初始形式,不会做布尔转换。
换句话说,一个或运算 ||
的链,将返回第一个真值,如果不存在真值,就返回该链的最后一个值
alert( 1 || 0 ); // 1(1 是真值)
alert( null || 1 ); // 1(1 是第一个真值)
alert( null || 0 || 1 ); // 1(第一个真值)
alert( undefined || null || 0 ); // 0(都是假值,返回最后一个值)
2
3
4
获取变量列表或者表达式中的第一个真值 我们用或运算
||
来选择有数据的那一个,并显示出来(如果没有设置,则用"Anonymous"
):let firstName = ""; let lastName = ""; let nickName = "SuperCoder"; alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder
1
2
3
4短路求值(Short-circuit evaluation)
||
对其参数进行处理,直到达到第一个真值,然后立即返回该值,而无需处理其他参数true || alert("not printed"); false || alert("printed");
1
2在第一行中,或运算符
||
在遇到true
时立即停止运算,所以alert
没有运行
# 3.5.2 &&(与)
两个 & 符号表示 &&
与运算符
result = a && b;
在传统的编程中,当两个操作数都是真值时,与运算返回 true
,否则返回 false
:
alert( true && true ); // true
alert( false && true ); // false
alert( true && false ); // false
alert( false && false ); // false
2
3
4
就像或运算一样,与运算的操作数可以是任意类型的值:
if (1 && 0) { // 作为 true && false 来执行
alert( "won't work, because the result is falsy" );
}
2
3
# 与运算寻找第一个假值
result = value1 && value2 && value3;
与运算 &&
做了如下的事:
- 从左到右依次计算操作数。
- 在处理每一个操作数时,都将其转化为布尔值。如果结果是
false
,就停止计算,并返回这个操作数的初始值。 - 如果所有的操作数都被计算过(例如都是真值),则返回最后一个操作数。
换句话说,与运算返回第一个假值,如果没有假值就返回最后一个值
// 如果第一个操作数是真值,
// 与运算返回第二个操作数:
alert( 1 && 0 ); // 0
alert( 1 && 5 ); // 5
// 如果第一个操作数是假值,
// 与运算将直接返回它。第二个操作数会被忽略
alert( null && 5 ); // null
alert( 0 && "no matter what" ); // 0
alert( 1 && 2 && null && 3 ); // null
//如果所有的值都是真值,最后一个值将会被返回
alert( 1 && 2 && 3 ); // 3,最后一个值
2
3
4
5
6
7
8
9
10
11
12
与运算 `&&` 在或运算 `||` 之前进行
与运算 &&
的优先级比或运算 ||
要高。
所以代码 a && b || c && d
跟 &&
表达式加了括号完全一样:(a && b) || (c && d)
注意
不要用 ||
或 &&
来取代 if
# 3.5.3 !(非)
感叹符号 !
表示布尔非运算符
result = !value;
逻辑非运算符接受一个参数,并按如下运作:
- 将操作数转化为布尔类型:
true/false
。 - 返回相反的值
alert( !true ); // false
alert( !0 ); // true
2
非运算符 !
的优先级在所有逻辑运算符里面最高,所以它总是在 &&
和 ||
之前执行
# !!
两个非运算 !!
有时候用来将某个值转化为布尔类型
# 3.5.4 空值合并运算符 '??'
空值合并运算符(nullish coalescing operator)的写法为两个问号 ??
由于它对待 null
和 undefined
的方式类似,所以在本文中我们将使用一个特殊的术语对其进行表示。为简洁起见,当一个值既不是 null
也不是 undefined
时,我们将其称为“已定义的(defined)”
a ?? b
的结果是:
- 如果
a
是已定义的,则结果为a
, - 如果
a
不是已定义的,则结果为b
let user;
alert(user ?? "匿名"); // 匿名(user 未定义)
2
let user = "John";
alert(user ?? "匿名"); // John(user 已定义)
2
我们还可以使用 ??
序列从一系列的值中选择出第一个非 null/undefined
的值
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
// 显示第一个已定义的值:
alert(firstName ?? lastName ?? nickName ?? "匿名"); // Supercoder
2
3
4
5
# 与 || 比较
或运算符 ||
可以以与 ??
运算符相同的方式使用
它们之间重要的区别是:
||
返回第一个 真 值。??
返回第一个 已定义的 值
# 优先级
??
运算符的优先级与 ||
相同,它们的的优先级都为 4
# ?? 与 && 或 || 一起使用
出于安全原因,JavaScript 禁止将 ??
运算符与 &&
和 ||
运算符一起使用,除非使用括号明确指定了优先级
# 3.6 循环
# 3.6.1 while循环
while
循环的语法如下:当 condition
为真时,执行循环体的 code
。
while (condition) {
// 代码
// 所谓的“循环体”
}
2
3
4
在 while
中的循环条件会被计算,计算结果会被转化为布尔值。
单行循环体不需要大括号",如果循环体只有一条语句,则可以省略大括号 {…}
:
let i = 3;
while (i) alert(i--);
2
# 3.6.2 do...while循环
使用 do..while
语法可以将条件检查移至循环体 下面:
do {
// 循环体
} while (condition);
2
3
循环首先执行循环体,然后检查条件,当条件为真时,重复执行循环体
不管条件是否为真,循环体 至少执行一次
# 3.6.3 for循环
for (begin; condition; step) {
// ……循环体……
}
2
3
语句段 | ||
---|---|---|
begin | let i = 0 | 进入循环时执行一次。 |
condition | i < 3 | 在每次循环迭代之前检查,如果为 false,停止循环。 |
body(循环体) | alert(i) | 条件为真时,重复运行。 |
step | i++ | 在每次循环体迭代后执行。 |
# 内联变量声明
“计数”变量 i
是在循环中声明的。这叫做“内联”变量声明。这样的变量只在循环中可见。
for (let i = 0; i < 3; i++) {
alert(i); // 0, 1, 2
}
alert(i); // 错误,没有这个变量。
2
3
4
# 省略语句段
for
循环的任何语句段都可以被省略
let i = 0; // 我们已经声明了 i 并对它进行了赋值
for (; i < 3; i++) { // 不再需要 "begin" 语句段
alert( i ); // 0, 1, 2
}
2
3
4
注意
请注意 for 的两个 ;
必须存在,否则会出现语法错误
# 3.6.4 break
循环中随时都可以使用 break 指令强制退出
let sum = 0;
while (true) {
let value = +prompt("Enter a number", '');
if (!value) break;
sum += value;
}
alert( 'Sum: ' + sum );
2
3
4
5
6
7
根据需要,"无限循环 + break
" 的组合非常适用于不必在循环开始/结束时检查条件,但需要在中间甚至是主体的多个位置进行条件检查的情况。
# 3.6.5 continue
continue
指令是 break
的“轻量版”。它不会停掉整个循环。而是停止当前这一次迭代,并强制启动新一轮循环(如果条件允许的话)
for (let i = 0; i < 10; i++) {
//如果为真,跳过循环体的剩余部分。
if (i % 2 == 0) continue;
alert(i); // 1,然后 3,5,7,9
}
2
3
4
5
禁止 `break/continue` 在 ‘?’ 的右边
请注意非表达式的语法结构不能与三元运算符 ?
一起使用。特别是 break/continue
这样的指令是不允许这样使用的
(i > 5) ? alert(i) : continue; // continue 不允许在这个位置,代码会停止运行,并显示有语法错误
# 3.6.6 break/continue 标签
使用标签可以一次从多层嵌套的循环中跳出来
标签 是在循环之前带有冒号的标识符:
labelName: for (...) {
...
}
2
3
break <labelName>
语句跳出循环至标签处:
outer:for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let input = prompt(`Value at coords (${i},${j})`, '');
// 如果是空字符串或被取消,则中断并跳出这两个循环。
if (!input) break outer; // (*)
// 用得到的值做些事……
}
}
alert('Done!');
2
3
4
5
6
7
8
9
标签并不允许“跳到”所有位置
break
指令必须在代码块内。从技术上讲,任何被标记的代码块都有效,例如:
label: {
// ...
break label; // 有效
// ...
}
2
3
4
5
continue
只有在循环内部才可行
# 3.7 switch
switch
语句可以替代多个 if
判断
# 3.7.1 switch语法
switch
语句有至少一个 case
代码块和一个可选的 default
代码块。
switch(x) {
case 'value1': // if (x === 'value1')
...
[break]
case 'value2': // if (x === 'value2')
...
[break]
default:
...
[break]
}
2
3
4
5
6
7
8
9
10
11
- 比较
x
值与第一个case
(也就是value1
)是否严格相等,然后比较第二个case
(value2
)以此类推 - 如果相等,
switch
语句就执行相应case
下的代码块,直到遇到最靠近的break
语句(或者直到switch
语句末尾) - 如果没有符合的 case,则执行
default
代码块(如果default
存在)
任何表达式都可以成为 `switch/case` 的参数
switch
和 case
都允许任意表达式。
let a = "1";
let b = 0;
switch (+a) {
case b + 1:
alert("this runs, because +a is 1, exactly equals b+1");
break;
default:
alert("this doesn't run");
}
2
3
4
5
6
7
8
9
# 3.7.2 switch中的break
let a = 2 + 2;
switch (a) {
case 3:
alert( 'Too small' );
break;
case 4:
alert( 'Exactly!' );
break;
case 5:
alert( 'Too big' );
break;
default:
alert( "I don't know such values" );
}//输出Exactly!
switch (a) {
case 3:
alert( 'Too small' );
case 4:
alert( 'Exactly!' );
case 5:
alert( 'Too big' );
default:
alert( "I don't know such values" );
}//输出Exactly! Too big I don't know such values
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3.7.3 case分组
共享同一段代码的几个 case
分支可以被分为一组
let a = 3;
switch (a) {
case 4:
alert('Right!');
break;
case 3: // (*) 下面这两个 case 被分在一组
case 5:
alert('Wrong!');
alert("Why don't you take a math class?");
break;
default:
alert('The result is strange. Really.');
}
2
3
4
5
6
7
8
9
10
11
12
13
switch/case
有通过 case 进行“分组”的能力,其实是 switch 语句没有 break
时的副作用。因为没有 break
,case 3
会从 (*)
行执行到 case 5
# 3.7.4 switch严格相等
switch的相等是严格相等。被比较的值必须是相同的类型才能进行匹配
# 3.8 函数
# 3.8.1 函数声明
function
关键字首先出现,然后是 函数名,然后是括号之间的 参数 列表,最后是花括号之间的代码(即“函数体”)
function showMessage() {
alert( 'Hello everyone!' );
}
2
3
我们的新函数可以通过名称调用:showMessage()
# 3.8.2 局部变量
在函数中声明的变量只在该函数内部可见
function showMessage() {
let message = "Hello, I'm JavaScript!"; // 局部变量
alert( message );
}
showMessage(); // Hello, I'm JavaScript!
alert( message ); // <-- 错误!变量是函数的局部变量
2
3
4
5
6
# 3.8.3 外部变量
# 外部变量读写
函数对外部变量拥有全部的访问权限。函数可以读写外部变量
let userName = 'John';
function showMessage() {
userName = "Bob"; // (1) 改变外部变量
let message = 'Hello, ' + *!*userName*/!*;
alert(message);
}
alert( userName ); // *!*John*/!* 在函数调用之前
showMessage();
alert( userName ); // *!*Bob*/!*,值被函数修改了
2
3
4
5
6
7
8
9
# 外部变量遮蔽
只有在没有局部变量的情况下才会使用外部变量。如果在函数内部声明了同名变量,那么函数会 遮蔽 外部变量
let userName = 'John';
function showMessage() {
let userName = "Bob"; // 声明一个局部变量
let message = 'Hello, ' + userName; // Bob
alert(message);
}
// 函数会创建并使用它自己的 userName
showMessage();
alert( userName ); // John,未被更改,函数没有访问外部变量。
2
3
4
5
6
7
8
9
# 3.8.4 参数
# 形参与实参
我们可以通过参数将任意数据传递给函数
函数被调用的时候,给定值被复制到了局部变量的形参上,然后函数使用它们进行计算
函数执行中会修改函数内的局部变量,但不会修改传递进来的实参,因为函数修改的是复制的实参值副本
function showMessage(from, text) {
from = '*' + from + '*'; // 让 "from" 看起来更优雅
alert( from + ': ' + text );
}
let from = "Ann";
showMessage(from, "Hello"); // *Ann*: Hello
// "from" 值相同,函数修改了一个局部的副本。
alert( from ); // Ann
2
3
4
5
6
7
8
- 形参(parameter)是函数声明中括号内列出的变量(它是函数声明时的术语)
- 实参(argument)是调用函数时传递给函数的值(它是函数调用时的术语)
# 形参默认值
如果一个函数被调用,但有参数(argument)未被提供,那么相应的值就会变成 undefined
我们可以使用 =
为函数声明中的参数指定所谓的“默认”(如果对应参数的值未被传递则使用)值
function showMessage(from, text = "no text given") {
alert( from + ": " + text );
}
showMessage("Ann"); // Ann: no text given
2
3
4
在 JavaScript 老代码中的默认参数
几年前,JavaScript 不支持默认参数的语法。所以人们使用其他方式来设置默认参数
显式地检查 undefined
:
function showMessage(from, text) {
if (text === undefined) {
text = 'no text given';
}
alert( from + ": " + text );
}
2
3
4
5
6
……或者使用 ||
运算符:
function showMessage(from, text) {
// 如果 text 的值为假值,则分配默认值
// 这样赋值 text == "" 与 text 无值相同
text = text || 'no text given';
...
}
2
3
4
5
6
# 3.8.5 返回值
函数可以将一个值返回到调用代码中作为结果
指令 return
可以在函数的任意位置。当执行到达时,函数停止,并将值返回给调用代码(分配给上述代码中的 result
)
只使用 return
但没有返回值也是可行的。但这会导致函数立即退出
空值的 `return` 或没有 `return` 的函数返回值为 `undefined`
function doNothing1() { /* 没有代码 */ }
alert( doNothing1() === undefined ); // true
function doNothing2() {
return;
}
alert( doNothing2() === undefined ); // true
2
3
4
5
6
不要在 `return` 与返回值之间添加新行
JavaScript 默认会在 return
之后加上分号,如果添加空行就会导致返回值变成空值
return
(some + long + expression + or + whatever * f(a) + f(b))
// 等价于
return;
(some + long + expression + or + whatever * f(a) + f(b))
2
3
4
5
如果我们想要将返回的表达式写成跨多行的形式,那么应该在 return
的同一行开始写此表达式
return (
some + long + expression
+ or +
whatever * f(a) + f(b)
)
2
3
4
5
# 3.8.6 函数表达式
函数表达式是一种创建函数的语法,它允许我们在任何表达式的中间创建一个新函数
let sayHi = function() {
alert( "Hello" );
};
2
3
由于函数创建发生在赋值表达式的上下文中(在 =
的右侧),因此这是一个 函数表达式。
请注意,function
关键字后面没有函数名。函数表达式允许省略函数名
# 函数是一个值
无论函数是如何创建的,函数都是一个值
我们还可以用 alert
打印这个变量的值:
function sayHi() {
alert( "Hello" );
}
alert( sayHi ); // 显示函数代码
2
3
4
最后一行代码并不会运行函数,因为 sayHi
后没有括号
在 JavaScript 中,函数是一个值,所以我们可以把它当成值对待。上面代码显示了一段字符串值,即函数的源码
# 函数声明vs函数表达式
函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用
在函数声明被定义之前,它就可以被调用
严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。
# 3.8.7 回调函数
在一个函数中将其他的函数当做参数传入,被传入的函数就是被称为 回调函数 或简称 回调
一个函数是表示一个“行为”的值
字符串或数字等常规值代表 数据。
函数可以被视为一个 行为(action)。
我们可以在变量之间传递它们,并在需要时运行。
# 3.8.8 箭头函数
创建了一个函数 func
,它接受参数 arg1..argN
,然后使用参数对右侧的 expression
求值并返回其结果。
let func = (arg1, arg2, ..., argN) => expression;
let sum = (a, b) => a + b;
/* 这个箭头函数是下面这个函数的更短的版本:
let sum = function(a, b) {
return a + b;
};
*/
alert( sum(1, 2) ); // 3
2
3
4
5
6
7
如果我们只有一个参数,还可以省略掉参数外的圆括号,使代码更短。
let double = n => n * 2; // 差不多等同于:let double = function(n) { return n * 2 } alert( double(3) ); // 6
1
2
3如果没有参数,括号则是空的(但括号必须保留):
let sayHi = () => alert("Hello!"); sayHi();
1
2
# 多行箭头函数
我们可以使用花括号将它们括起来。主要区别在于,用花括号括起来之后,需要包含 return
才能返回值(就像常规函数一样)
let sum = (a, b) => { // 花括号表示开始一个多行函数
let result = a + b;
return result; // 如果我们使用了花括号,那么我们需要一个显式的 “return”
};
alert( sum(1, 2) ); // 3
2
3
4
5