ASI
在写JS之前,我一直在写Python,习惯了没有分号的代码。
刚好,JS为我们提供了 自动分号插入 Automatic Semicolon Insertion
!
这让我们在大部分情况下都不用写分号,非常的优雅。
然而ASI在某些情况下将产生错误。
ASI发生错误的情况
IIFE 立即调用函数表达式
考虑以下代码。
代码语言:javascript复制let a = 1
(function log() {
console.log(a)
})()
我们很容易就能看懂这段代码的意思,首先定义了值为1的变量a,然后定义了一个函数log用来输出a的值并且立即调用它。
按理说这段代码的执行结果是输出1,但是实际上却报错了。
代码语言:javascript复制> node 1.js
/root/1.js:2
(function log() {
^
TypeError: 1 is not a function
提示显示1不是一个函数,看来引擎把代码理解成了这个样子。
代码语言:javascript复制let a = 1(function log() {console.log(a)})()
想去call 1,这自然会报错。
利用解构语语法swap的时候
代码语言:javascript复制let a = 1, b = 2
[a, b] = [b, a]
console.log(`a: ${a}`)
console.log(`b: ${b}`)
很容易看出这段代码的意思是交换变量a和b的值,然后分别输出。
然而又报错了。
代码语言:javascript复制> node 1.js
/root/1.js:2
[a, b] = [b, a]
^
ReferenceError: Cannot access 'b' before initialization
和IIFE一样,引擎把代码理解成了
代码语言:javascript复制let a = 1, b = 2[a, b] = [b, a]
逗号后面是一个连等,需要从右边往左看,即先看2[a, b] = [b, a]
而这个赋值语句中已经用到了b,而这个时候b还没有初始化,所以由于暂时性死区的原因,报错了。
至少是报错了,我们可以知道某个地方出了问题,如果没有报错呢?
代码语言:javascript复制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}`)
这段代码不会报错,以下是它的输出结果
代码语言:javascript复制> node 1.js
a: 4
b: 3
c: 3
d: 4
js把中间的两个swap看成了
代码语言:javascript复制[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 = [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