跳到主要内容

语句

表达式语句

表达式语句 一般由带副作用的表达式构成,比如赋值、函数调用等。

复合语句 & 空语句

语句块可以将多个语句合为一个复合语句。可以放在任何只期待一个语句的地方:

{
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 中如果在最后没有使用 breakreturn 等语句,则会直接执行下一条 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]) // 打印每个属性的值
}

如果循环对象为 nullundefined ,则会直接跳过这个循环语句;否则,会对对象中每个可枚举的属性执行一次循环体。

不建议对数组使用 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 ;

  • 对象字面量中不能多次定义同名属性;

  • 不允许使用八进制前缀表示整数字面量;

  • evalarguments 会当作关键字,不允许以该标识符进行语句声明;

  • 检查调用栈的能力是受限制的,访问 arguments.callerargument.callee 会抛出 TypeError 。

声明

严格来说,声明并不是语句,它们更像是程序结构的一部分,会在代码运行前预先处理。

声明有 constletvarfunctionclassimportexport 关键字。

var 声明的变量,其作用域为函数,而非块。现代 JS 中没有任何理由再使用 var 而不是 let

参考

Error - MDN

try...catch - MDN

语句和声明 - MDN

debugger - MDN