语句
表达式语句
表达式语句 一般由带副作用的表达式构成,比如赋值、函数调用等。
复合语句 & 空语句
语句块可以将多个语句合为一个复合语句。可以放在任何只期待一个语句的地方:
{
    let name = "Talaxy"
    console.log(name)
    // ...
}
也可以仅用一个 ; 表示一条空语句。
条件语句
条件语句是代码中需要决策的地方,也可称为分支。
if 语句
if 是最基本的条件语句,有两种形式:
if (条件表达式) 执行语句
if (条件表达式)
    执行语句
else
    执行语句
如果尝试在 if 里嵌套 if ,那么 else 会跟从它就近的 if 语句:
if (条件表达式)
    if (条件表达式)
        执行语句
else  // else 会跟从第二个 if ,而非第一个
    执行语句
因此,即便执行语句只有一条,都应当使用花括号,避免歧义:
if (条件表达式) {
    if (条件表达式) {
        执行语句
    }
} else {
    执行语句
}
多个条件用 else if :
if (条件表达式) {
    执行语句
} else if (条件表达式) {
    执行语句
} else if (条件表达式) {
    执行语句
} else {
    执行语句
}
switch 语句
switch 会根据表达式可能出现的情况进行分支:
switch (表达式) {
case 情况一:       // 如果 表达式 === 情况一
    执行语句
case 情况二:       // 如果 表达式 === 情况二
    执行语句
/* ... */
default:          // 如果前面的情况没有覆盖到
    执行语句
}
case 后可以给定任意表达式,而非仅于常量,且使用的是严格相等 === 来与 switch 后的表达式进行匹配。同时,在 case 中如果在最后没有使用 break 或 return 等语句,则会直接执行下一条 case 的执行语句。
default: 标签是可选的,实际上该标签可以出现在 switch 语句体中的任何位置。
因为
case后的表达式是在运行时求值,所以(尤其在性能上)与 C、C++、Java 的 switch 语句有很大差别。
循环语句
while 语句
和 if 语法类似,会在表达式为真的情况下循环执行循环体中的语句:
while (条件表达式) {
    执行语句
}
do-while 语句
和 while 语句不同的是,do-while 会从尾部判断循环,所以循环体会被至少执行一次:
do {
    执行语句
} while (条件表达式)
for 语句
for 语句可以基于计数器进行语句循环:
for (初始化语句; 条件表达式; 递增语句) {
    执行语句
}
最简单的应用:
for (let count = 0; count < 10; count += 1) {
    console.log(count)
}   // 打印 0 到 9
for-of 语句
ES6 新定义了 for-of 语句,专门用于可迭代对象(比如数组):
let data = [1, 2, 3, 4], sum = 0
for (const num of data) {
    sum += num
}
sum   // => 10
字符串也是可逐字迭代的,且是按照 Unicode 码点进行迭代的:
for (const c of "hello") {
    // ...
}
ES6 的 Set 和 Map 也是可迭代的,其中 Map 对象可以在迭代中解构键值对:
let m = new Map([[1, "one"], [2, "two"]])
for (const [key, value] of m) {
    key   // => 1, 2
    value // => "one", "two"
}
同时,这种迭代是实时的,比如在迭代数组时不断地在尾部插入元素,则会无穷循环下去。
普通的对象是不可迭代的,尝试对普通对象使用 for-of 会抛出 TypeError 。可以用 for-in 语句,或者通过 Object.keys() 来创建一个可迭代的键表:
let obj = { x: 1, y: 2, z: 3 }
let keys = ""
for (const k of Object.keys(obj)) {
    keys += k
}
keys  // => "xyz"
ES2018 还新增了异步迭代器:
async function printStream(stream) {
    for await (let chunk of stream) {
        console.log(chunk)
    }
}
for-in 语句
for-in 循环的 in 后面可以是任意对象:
for (let prop in obj) {
    console.log(obj[prop])  // 打印每个属性的值
}
如果循环对象为 null 或 undefined ,则会直接跳过这个循环语句;否则,会对对象中每个可枚举的属性执行一次循环体。
不建议对数组使用 for-in 语句来迭代元素,因为不能保证按索引顺序进行访问,以及是否有元素外的可枚举属性。
又比如,每个对象都有 toString() 方法,但该方法是不可枚举的,所以 for-in 不会枚举改属性。
如果在执行循环的时候删除了还未枚举的属性,则该属性就不会被枚举了;反之,添加新属性可能会被枚举。
跳转语句
顾名思义,跳转语句会导致 JS 解释器执行位置的跳跃。
语句标签
通过标识符和一个冒号 : ,可以为任何语句加上标签(但是一般只有循环或条件语句才有意义):
mainloop: while(token !== null) {
    // ...
    continue mainloop   // 跳到命名循环的下一次迭代
}
语句标签的标识符可以是任何合法的 JS 标识符;且与变量、函数不在同一个命名空间,因此同一个标识符既可以为标签,也可以是变量、函数名。而有嵌套关系的两条语句不能使用同名标签。
break 语句
break 语句会跳出循环或 switch 语句。如果没有指明语句标签,则会跳出最近的循环或 switch 语句。
break 的控制权仅限于函数边界内,你不能在一个函数外部定义标签,然后在函数内部使用 break 跳出。
continue 语句
continue 语句与 break 类似,但是他不会跳出循环,而是执行下一次迭代。
return 语句
return 用于函数调用的值返回。如果 return 没有指定一个返回值的表达式,则会返回 undefined 。
yield 语句
yield 用于生成器函数中,用来产出一个迭代值。
throw 语句
throw 用于抛出异常。可以抛出一个 new Error(错误信息) 。
出现异常后,JS 解释器会立即终止当前程序的执行,并寻找就近的异常处理 catch 语句;如果没有找到,则将异常作为错误报告给用户。
try-catch-finally
try-catch-finally 是异常处理语句。try 子句用于定义可能存在异常的语句;catch 会捕获 try 子句中的错误;finally 为最终会执行的语句,无论是否有异常。
try {
    // 可能会出现异常的语句
} catch (error) {
    // 捕获错误,并处理异常
} finally {
    // 执行最终的工作
}
在 catch(以及 finally )中也可以继续用 throw 抛出异常。此外,可以定义多个 catch 块并指定其可能的类型,来针对不同类型的异常进行处理,可见 条件 catch 块 - MDN。
在 ES2019 及后,可以省略 catch 后的圆括号及标识符,来表示对错误信息的忽略。
其他语句
with 语句
with 用于方便访问及操作深度嵌套的对象:
let obj = { x: 1, y: 2, z: 3 }
with (obj) {
    x = 4
    console.log(y)    // 打印 2
}
指定对象的属性就好像成为了代码块作用域中的变量一样。但是 with 语句在严格模式是禁用的。
debugger 语句
debugger 语句调用任何可用的调试功能,例如设置断点。如果没有调试功能可用,则此语句不起作用。具体可见 debugger - MDN 。
"use strict" 指令
"use strict" 是 ES5 引入的指令,该指令必须位于脚本或函数体等的开头。严格模式 是 JS 一个受限的子集,它规避了重要的语言缺陷,提供更强的错误检查,增强了安全性。
严格模式的效果会同样作用到 eval() 中。任何 class 体或 ES6 模块中的代码全部默认为严格代码,无需手动标注 "use strict" 。
严格模式有如下限制:
禁用 with 语句;
所有变量必须声明,使用未声明变量会抛出 ReferenceError ;
函数作为函数(而非方法)调用时,
this值为undefined,而非严格模式为全局对象;给不可写的属性赋值,或者尝试在不可扩展的对象上添加新属性,删除不可配置的属性,会抛出 TypeError ;
在
eval()中声明的变量或函数会在eval()返回时销毁;不能用
delete删除变量、函数、函数参数,会抛出 SyntaxError ;对象字面量中不能多次定义同名属性;
不允许使用八进制前缀表示整数字面量;
eval和arguments会当作关键字,不允许以该标识符进行语句声明;检查调用栈的能力是受限制的,访问
arguments.caller和argument.callee会抛出 TypeError 。
声明
严格来说,声明并不是语句,它们更像是程序结构的一部分,会在代码运行前预先处理。
声明有 const、let、var、function、class、import、export 关键字。
var 声明的变量,其作用域为函数,而非块。现代 JS 中没有任何理由再使用 var 而不是 let 。