Menu

JavaScript 语法与分号:ASI 规则详解

JavaScript 语法如何工作:语句、代码块、表达式,以及关于分号、自动分号插入和它何时坑你的真实情况。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

JavaScript 代码是一串语句

JavaScript 程序就是一系列语句,也就是引擎从上到下执行的指令。声明变量是一条语句,调用函数是一条语句,if 或 for 代码块也是一条语句。语句之间用分号分隔。

三条语句,三个分号。引擎先执行第一条,再执行第二条,最后执行第三条,就这么简单。

空白和缩进对解析器没有任何意义。你可以把这三条语句用分号隔开写在同一行,运行结果完全一样。缩进是为了让人读得懂,而不是为了引擎。

代码块把语句组合在一起

花括号 { } 创建一个代码块,也就是引擎当作一个整体对待的一组语句。代码块随处可见:函数体、if 分支、循环体。

花括号里的两次 console.log 调用就是 if 的主体。注意,右花括号 } 后面没有分号,因为代码块不是需要结束符的语句。整个 if 语句在代码块结束时就结束了。

从其他语言转过来的人常在这里犯错,因为那些语言里每个 } 后面都要跟一个 ;。在 JavaScript 中,独立的代码块或控制流代码块后面不需要分号。

表达式与语句

有一个区别值得尽早弄清楚:表达式会产生一个值,语句则是执行某个操作。

  • 2 + 3 是一个表达式,它的计算结果是 5。
  • let x = 2 + 3; 是一条语句,它声明一个变量,并把一个表达式的结果赋给它。
  • console.log("hi") 是一个表达式(函数调用会返回 undefined),但单独写成一行并加上分号,它就成了表达式语句。

大多数时候你不用考虑这些。等你遇到箭头函数、三元运算符,或者 JavaScript 需要表达式而你给了它一条语句(或者反过来)的时候,这个区别就重要了。

分号问题

老实说:JavaScript 有一个叫自动分号插入(ASI)的特性,会在大多数换行处替你补上缺失的分号。所以下面这样的代码可以正常运行:

没有分号,也没有报错。ASI 会在每个换行处判断:“下一个记号能接续当前语句吗?”如果不能,它就插入一个分号。

正因为有 ASI,实际项目中存在两种都合理的风格:

  • 总是写分号。 大多数代码库、大多数教程和大多数风格指南都这样做。
  • 不写分号。 一些现代项目在用(Standard 风格、部分 React 代码库),依靠 ASI 再加上几个防御性技巧。

两种都能用,谁也不算“错”。真正错的是不一致:在同一个文件里混用两种风格,会让 bug 更难发现。

ASI 在哪里会坑你

ASI 在大约 99% 的情况下都做得对。剩下的 1% 是那些以能够接续上一行的记号开头的行。惹麻烦的就是 [、(、`、+、- 和 /。

看看这段代码:

你以为 x 和 y 会互换。实际上 ASI 不会在 [x, y] 前面插入分号,因为 10[x, y] 在语法上是合法的(对数字 10 做索引)。解析器把第 2 行和第 3 行读成一个大表达式,结果你会得到运行时错误或者乱七八糟的输出。

解决办法之一是在第 2 行末尾加分号:

或者,如果你用的是不写分号的风格,就在有风险的那一行开头加一个分号:

开头的 ; 就是不写分号的代码库所用的防御性技巧。在你知道它为什么存在之前,它看起来很奇怪。

return 陷阱

有一种 ASI 情况值得牢记,因为它会产生悄无声息的 bug。如果你在 return 后面直接换行,ASI 会在那里插入分号,函数就返回 undefined:

作者本来想返回一个对象。ASI 看到行尾的 return,当场结束了这条语句,对象字面量就成了永远执行不到的代码。

解决办法是让返回值和 return 从同一行开始:

同样的规则也适用于 throw、break、continue 和 yield:不要在关键字和它的值之间换行。

一条简单好用的规则

如果你刚开始学,最省事的做法是:

  1. 在每条语句末尾显式写分号。
  2. if、for、while、函数声明或类体的结束 } 后面不写分号。
  3. 对象字面量或赋值给变量的函数表达式,} 后面要写分号:const f = function() {};。
  4. 使用格式化工具(Prettier,或配置了风格规则的 ESLint),然后就不用再操心了。格式化工具会执行你的团队定好的风格。

这条规则不是放之四海而皆准的定律,而是你读到的大多数 JavaScript 代码遵循的约定。在你有理由改变之前,就照着它来。

大小写敏感与标识符

还有两条小规则也放在这里:

  • JavaScript 区分大小写。userName、username 和 UserName 是三个不同的标识符。
  • 标识符可以包含字母、数字、_ 和 $,但不能以数字开头,也不能是 class、return 或 function 这样的保留字。

$ 是合法的,但按照惯例通常留给特定库使用(jQuery 以前就用它,一些模板系统现在还在用)。你自己很少会选它。

下一节:严格模式

现代 JavaScript 往往悄悄运行在一种更严格的语言变体里,叫作严格模式,它会把一些马虎的写法变成真正的错误。ES 模块和类体默认就是严格模式,但它到底改变了什么,还是值得了解一下,这就是下一页的内容。

常见问题

JavaScript 需要写分号吗?

严格来说不需要:JavaScript 有自动分号插入(ASI),会在大多数换行处替你补上分号。但实际上,大多数团队仍然显式地写分号,因为 ASI 有几种边界情况(以 [、(、`、+、-、/ 开头的行)会出错。选定一种风格,然后让 Prettier 这样的格式化工具来保证统一。

JavaScript 的自动分号插入是什么?

ASI 是一条规则:当下一个记号无法接续当前语句时,JavaScript 会在行尾插入缺失的分号。所以 let x = 1 不写分号也能运行。如果某一行恰好以能够接续上一行的符号开头,比如 [ 或 (,ASI 就会失效,JavaScript 会悄悄把两行粘成一句。

JavaScript 语句的基本语法是什么?

语句就是一条指令:变量声明、函数调用、if 代码块、循环。语句之间用分号分隔(显式写的或自动插入的)。一组语句放在 { } 里组成代码块。解析器会忽略空白和缩进,所以格式是给人看的,不是给引擎看的。

Coddy 编程语言插图

和 Coddy 一起学编程

开始