语句
1. 表达式语句
- 最简单的一种语句就是有副效应的表达式。
- 赋值语句是一种主要的表达式语句。
2. 复合语句与空语句
通过语句块(代码块)可以将多个语句组合为一个复合语句:
{
x = Math.PI;
cx = Math.cos(x);
console.log("cos(n) = " + cx);
}
复合语句允许在 JavaScript 语法期待一个语句时使用多个语句。空语句正好相反,表示不包含任何语句:
; // 空语句
空语句偶尔有用,如创建空循环体的循环:
// 初始化数组a
for (let i = 0; i < a.length; a[i++]=0);
3. 条件语句
条件语句也称为分支语句。
3.1 if
if
条件后就算只有一行语句,也建议使用代码块包裹。
3.2 else-if
用于根据条件执行多段代码中的一段:
if (n === 1) {
} else if (n === 2) {
} else if (n === 3) {
} else {
}
3.3 switch
switch
关键字后跟一个带括号的表达式和代码块,执行时,会计算表达式的值,然后对比 case
标签,相等(===)则执行对于代码块。如果没有找到相等的值,则找 default
标签,如果没 default
,则跳过整个代码块。
switch(n) {
case 1:
// 执行第一个代码块
break;
case 2:
// 第二个
break;
case 3:
break;
default:
break;
}
注意
- 如果
case
后的代码块中没break
语句,则会继续执行下一个case
。 - 在函数中使用
switch
时可以用return
替换break
,后续代码不会执行。 case
后可跟任意代码块,通过全等===
与switch
表达式值进行匹配。
4. 循环语句
4.1 while
while (expression) {
statement
}
4.2 do-while
与 while
的区别:
do while
循环体至少会执行一次。do
循环必须以分号结束,而while
循环体使用花括号时不需要分号。
let len = a.length, i = 0;
if (len === 0) {
console.log("Empty Array")
} else {
do {
console.log(a[i])
} while (++i < len); // 必须跟分号
}
4.3 for
for (initialize; test; increment) {
statement
}
- initialize、test、increment 分别代表初始化、测试和递增三个表达式,初始化只执行一次。
- 三个表达式中任何一个都可以省略,只有两个分号是必需的。
for(;;)
与while(true)
一样,是无限循环。
4.4 for/of
for/of
循环专门用于可迭代对象,如数组、字符串、集合和映射。
let data = [1, 2, 3, 4, 5], sum = 0;
for (let element of data) {
sum += element;
}
sum // 15
对数组使用 for/of
迭代是实时的,在循环体内改变数组会影响循环结果,如循环体内想数组添加元素,则会造成无限循环。
4.4.1 for/of 与对象
- 对象默认不可迭代,对常规对象使用
for/of
会报错。 - 如果想迭代对象属性,可使用
for/in
或Object.keys()
方法,该方法会返回对象属性数组。 Object.values()
会返回对象的属性值构成的数组,Object.entries()
返回数组构成的数组,每个数组元素是对象键值对构成的数组。
let pairs = "";
for (let [k, v] of Object.entries(o)) {
pairs += k + v
}
4.4.2 for/of 与字符串
字符串按照码点进行迭代,而不是 UTF-16 字符(码元),有的字符可能长度为 2,占据两个码元,但只遍历一次。
4.4.3 for/of 与 Set 和 Map
for/of
迭代Set
时,会把所有元素遍历一遍。for/of
迭代Map
时,迭代的是键值对数组,类似于迭代Object.entries()
。
let m = new Map([[1, "one"]])
for (let [k, v] of m) {
key // 1
value // "one"
}
4.4.4 for/await 与异步迭代
ES2018 新增的一种 for/of
循环,需使用异步迭代器:
// 从异步可迭代流中读取数据块并打印
async function printStream(stream) {
for await (let chunk of stream) {
console.log(chunk);
}
}
4.5 for/in
for/in
循环后面可以是任意对象,for/of
只能是可迭代对象。for/in
是一开始就有的,for/of
是 ES6 新增的。for/in
用于遍历对象属性,但不会遍历所有属性,比如名为Symbol
的属性,只能遍历可枚举的属性。JavaScript 核心定义的内部方法是不能枚举的,如toString()
。- 默认情况下,手写代码定义的所有属性和方法都是可枚举的。
5. 语句
5.1 语句标签
通过前置一个标识符和冒号,可以给任何语句加标签:identifier: statement
。
一般也就是给循环语句加标签,然后用 break
或 continue
进行跳转:
mainloop: while(token !== null) {
...
continue mainloop; // 跳到命名循环的下一次迭代
}
注意
- 如果两条语句有嵌套关系,则不能使用相同的标签。
break
和continue
是JavaScript 中唯一使用语句标签的语句。
5.2 break
break
单独使用时,会导致循环或switch
语句立即退出。break
后可跟标签,会跳转到指定标签语句的末尾或终止该语句。如果想中断一个并非最接近的嵌套循环或switch
语句,就要使用带标签的break
语句。
let matrix = getData()
let sum = 0, success = false;
computeSum: if (matrix) {
for (let x = 0; x < matrix.length; x++) {
let row = matrix[x];
if (!row) break computeSum;
for (let y = 0; y < row.length; y++) {
let cell = row[y];
if (isNaN(cell)) break computeSum;
sum += cell;
}
}
success = true;
}
// 如果 success 为 false,表明矩阵有非法值,否则,计算矩阵元素和
5.3 continue
continue
语句与 break
类似,但 continue
不会退出循环,而是跳过当前循环,从头开始执行循环的下一次迭代。
注意
- 无论带不带标签,
continue
都只能在循环体内使用。 continue
带标签,标签只能用于循环语句,否则会报错。
5.4 return
return
语句只能出现在函数体内,否则会报错。- 函数体内没有
return
,返回undefined
。 - 不能在
return
和返回值之间插入换行。
5.5 yield
yield
语句非常类似 return
,但只能用在生成器函数中,以回送生成的值序列的下一个值。
function* range(from, to) {
for (let i = from; i <= to; i++) {
yield i;
}
}
5.6 throw
- 在 JavaScript 中,每当运行时发生错误或程序里使用
throw
语句时都会抛出异常,可以使用try/catch/finally
语句捕获异常。 - 语法:
throw expression
,其中expression
可以是任何值,如表示错误码的数值,或者可读的错误消息字符串。 - JavaScript 解释器在抛出错误时会使用
Error
类及其字类,在自己代码中也可以使用这些类。Error
对象有一个 name 属性和一个 message 属性,分别用于指定错误类型和保存传入构造函数的字符串。
function factorial(x) {
if (x < 0) throw new Error('x必须大于0');
let f;
for (f = 1; x > 1; f *= x, x--);
return f;
}
注意
- 抛出异常时,JavaScript 程序会立即停止程序的执行并跳到最近的异常处理程序。
- 异常是沿着方法的词法结构和调用栈向上传播的,如果没找到任何异常调用程序,则将异常作为错误报告给用户。
5.7 try/catch/finally
try/catch/finally
语句是 JavaScript 中的异常处理机制,try
用于定义要处理异常的代码块,catch
在发生异常时被调用,finally
无论是否发生异常,一定会执行,通常用于执行清理。catch
和finally
是可选的,但只要有try
,则必须有二者中的一个。catch
后通常会跟一个包含在圆括号中的标识符,类似函数参数,当捕获到异常时,与异常关联的值会赋值给这个参数。该参数具有块作用域,即只在catch
块中有用。- 如果由于
return
、continue
或break
等跳转语句离开了 try 块,在跳转之前会执行finally
块。 - 如果
finally
子句抛出异常,该异常会代替正被抛出的其它异常;如果执行了return
语句,则相应方法正常返回,即使有正在抛出且尚未处理的异常。
try {
let n = Number(prompt("请输入一个正整数", ""));
let f = factorial(n);
alert(n + "!=" + f);
} catch (e) {
alert(e)
}
6. 其它语句
6.1 with
- 语法:
with(object) statement
。这个语句创建了一个临时作用域,以object
对象的属性作为变量,然后在该作用域中执行代码。 - 使用
with
语句主要是为了更方便的使用深度嵌套对象:
document.form[0].address.value = ''
// 通过 with 简写
with(document.form[0]) {
address.value = ''
}
注意
严格模式下禁止使用 with
,非严格模式也不应该使用,因为会造成代码难以优化。
6.2 debugger
用于调试,类似于断点,如果使用浏览器并打开了开发者控制台,这个语句就会导致断点。
6.3 "use strict"
"use strict"
指令用于开启严格模式,不是语句,只能出现在脚本或函数体的开头。- 除了显式声明严格模式,任何位于
class
体或 ES6 模块中的代码全部默认为严格代码。
严格模式与非严格模式的区别
- 严格模式下不允许使用
with
语句。 - 严格模式下,所有变量都必须声明。
- 严格模式下,函数如果作为函数被调用(而不是方法),其
this
值为undefined
(非严格模式下为全局对象)。 - 等等。
7. 声明
7.1 const、let 和 var
ES6 之后使用 const
声明常量 let
声明变量,ES6 之前只能使用 var
声明变量,不能声明常量。
7.2 function
function
声明用于定义函数:
function area(radius) {
return Math.PI * radius * radius;
}
- 无论在作用域中什么地方声明函数都会被“提升”,因此调用函数的代码可能位于声明函数的代码之前。
- 生成器声明使用
function
关键字后跟一个星号。
7.3 class
ES6 之后,可以用 class
创建类,与函数不同,类声明不会提升,因此不能在还没声明类时就先使用类。
class Circle {
constructor(radius) {
this.r = radius;
}
area() {
return Math.PI * this.r * this.r;
}
}
7.4 import 和 export
- JavaScript 中一个代码文件就是一个模块,有自己的全局作用域(模块作用域),完全与其它模块无关。
import
指令用于从其它模块中导入一个或多个值,并为这些值指定名字。
import Circle from './circle.js'
import { PI, TAU } from './constants.js'
import { magnitude as hypotenuse } from './utils.js'
export
指令用于把当前一个或多个值导出,导出后其它模块才能导入这些值。
const PI = Math.PI;
const TAU = 2 * PI;
export {
PI,
TAU
}
export
关键字也可用作其它声明的标识符,构成复合声明,在定义变量、常量、函数或类的同时导出它们。
export const TAU = 2 * PI;
export function magnitude(x, y) {
return Math.sqrt(x * x + y * y);
}
- 如果一个模块只导出一个值,通常会使用特殊的
export default
形式。
export default class Circle {
}