JS该写分号嘛?

ASI

在写JS之前,我一直在写Python,习惯了没有分号的代码。

刚好,JS为我们提供了 自动分号插入 Automatic Semicolon Insertion

这让我们在大部分情况下都不用写分号,非常的优雅。

然而ASI在某些情况下将产生错误。

ASI发生错误的情况

IIFE 立即调用函数表达式

考虑以下代码。

1
2
3
4
let a = 1
(function log() {
console.log(a)
})()

我们很容易就能看懂这段代码的意思,首先定义了值为1的变量a,然后定义了一个函数log用来输出a的值并且立即调用它。

按理说这段代码的执行结果是输出1,但是实际上却报错了。

1
2
3
4
5
6
> node 1.js
/root/1.js:2
(function log() {
^

TypeError: 1 is not a function

提示显示1不是一个函数,看来引擎把代码理解成了这个样子。

1
let a = 1(function log() {console.log(a)})()

想去call 1,这自然会报错。

利用解构语语法swap的时候

1
2
3
4
let a = 1, b = 2
[a, b] = [b, a]
console.log(`a: ${a}`)
console.log(`b: ${b}`)

很容易看出这段代码的意思是交换变量a和b的值,然后分别输出。

然而又报错了。

1
2
3
4
5
6
> node 1.js
/root/1.js:2
[a, b] = [b, a]
^

ReferenceError: Cannot access 'b' before initialization

和IIFE一样,引擎把代码理解成了

1
let a = 1, b = 2[a, b] = [b, a]

逗号后面是一个连等,需要从右边往左看,即先看2[a, b] = [b, a]

而这个赋值语句中已经用到了b,而这个时候b还没有初始化,所以由于暂时性死区的原因,报错了。

至少是报错了,我们可以知道某个地方出了问题,如果没有报错呢?

1
2
3
4
5
6
7
8
9
let a = 1, b = 2, c = 3, d = 4
if (a < b) {
[a, b] = [b, a]
[c, d] = [d, c]
}
console.log(`a: ${a}`)
console.log(`b: ${b}`)
console.log(`c: ${c}`)
console.log(`d: ${d}`)

这段代码不会报错,以下是它的输出结果

1
2
3
4
5
> node 1.js
a: 4
b: 3
c: 3
d: 4

js把中间的两个swap看成了

1
[a, b] = [b, a][c, d] = [d, c]

仍然是一个连等,我们需要从右往左看,首先是[b, a][c, d] = [d, c]

左边的[b, a][c, d] 实际上是 [2, 1][3, 4]

你可能会说卧槽,这他妈什么东西。

我们慢慢看。首先[2, 1]是一个数组。

然后它后面的[3, 4]实际上是一个下标选择,里面的3, 4会被看成一个逗号表达式,它的值是最后一个元素,即4。

所以[2, 1][3, 4]可以写为[1, 2][4],所以最后的值是undefined。

所以总的语句就变成了[a, b] = undefined = [d, c]

你可能又想吐槽undefined = [d, c]算什么鬼,实际上undefined是可以作为左操作数的,只不过静默失败。

undefined赋值静默失败

然后值得注意的是,undefined = [d, c]虽然对undefined本身没有影响,但是它本身作为一个赋值表达式也是有值的,它的值就是[d, c]

故最后运行的表达式实际上是这样的[a, b] = [d, c],即把d的值给a,c的值给b。

这种没有报错的隐式错误,真在Leetcode刷题的时候 够你Debug半天了2333。

总结

实际上,完全不用为了ASI在一些情况下导致错误而每行都加上一个分号。

对于我目前遇到的这两种情况,可以总结一下,如果一行的开始是一个(或者[,那么再上一行你需要手动加上一个分号,至于其他的情况,完全不用考虑分号~

还可以参考一下尤大的这篇回答 https://www.zhihu.com/question/21076930/answer/17135846


JS该写分号嘛?
https://wuuconix.link/2022/08/06/asi/
作者
wuuconix
发布于
2022年8月6日
许可协议