什么是ASI?自动分号插进去(automaticsemicoloninsertion,ASI)是一种顺序剖析手艺,它在JavaScript顺序的语法分析(parsing)阶段起作用
什么是 ASI ?
自动分号插进去 (automatic semicolon insertion, ASI) 是一种顺序剖析手艺,它在 Javascript 顺序的语法分析 (parsing) 阶段起作用。
依据 ES2015 范例,某些(不是悉数) Javascript 语句需要用 ;
来示意语句的完毕;然则为了轻易誊写,在某些状况下这些分号是可以从源码中省略的,此时我们称 ;
被 parser 自动插进去到标记流 (token stream) 中,这类机制称为 ASI。
所谓的“自动分号插进去”着实只是一种抽象的形貌,parser 并没有真的插进去一个个分号。ASI 只是示意编译器准确明白了顺序员的企图。听起来就像编译器对顺序员说:“Hey,哥们!虽然这里你没写分号,但我晓得你想说这条语句已完毕了。”
需要用 ;
来示意完毕的语句是:
- 空语句
- 以
let
、 const
、import
、 export
开首的声明语句 - 以
var
开首的变量声明语句 - 表达式语句
-
debugger
语句 -
continue
语句 -
break
语句 -
return
语句 -
throw
语句
并不是一切的 Javascript 语句都需要用 ;
示意完毕,比方:
这些语句原本就不需要 ;
示意完毕。
举例来说,函数声明 (Function declaration, FD) 不需要以分号完毕:
function add10(num) {
return num + 10;
} // I don't need a semicolon here
假如你弄巧成拙地在 FD 以后写了一个 ;
,它会被剖析为一条空语句。
ASI 划定规矩
ASI 是备胎(第二挑选)
编译器不会优先启用 ASI 机制。实际上,在碰到行完毕符 (Line Terminator) 时,编译器老是先试图将行完毕符分开的标记流看成一条语句来剖析(着实有少数几个惯例:return
、throw
、break
、continue
、yield
、++
、 --
,随后会引见),着实不符合准确语法的状况下,才会退而求其次,启用 ASI 机制,将行完毕符分开的标记流看成两条语句(俗称,插进去分号)。来看下面的例子:
var a = 0
var b = 1
这个简朴代码段的标记流为:
var a = 0 \n var b = 1
parser 从左至右剖析这个标记流,剖析过程当中它碰到了换行符 LF ( \n
, 行完毕符之一)。它看起来如许喃喃自语:“我碰到了一个换行符,让我先尝尝去掉它,把这个代码段看成一条语句尝尝!”
因而 parser 实际上先剖析了如许一条语句:
var a = 0 var b = 1
// Uncaught SyntaxError: Unexpected token var
很显然这是一条有语法错误的语句,此路不通!
parser 说:“这个标记流假如看成一条语句的话,是有语法错误的!这该怎么办呢?我是否是要就此摒弃、直接抛出语法错误呢?不!我可是要成为海贼王的男子!我要启用 ASI 机制尝尝。”
因而不折不挠的 parser 又剖析了下面的语句:
var a = 0; var b = 1 // legal syntax
Bingo! 没有 SyntaxError ,剖析经由过程!
parser 因而自满地对顺序员说:“Hey,哥们!虽然在 \n
前面你没写分号,但我晓得你想说 var a = 0
这条赋值语句已完毕了!”
“高!着实是高!”
软弱的标记、被误会的源码
需要注重的是,parser 对标记流的这类处置惩罚机制有时会致使它误会顺序员的企图。
var a = [1, [2, 3]]
[3, 2, 1].map(function(num) {
return num * num;
})
由于 parser 老是优先将换行符前后的标记流看成一条语句剖析,parser 实际上先剖析了下面的语句:
var a = [1, [2, 3]][3, 2, 1].map(function(num) {
return num * num;
})
这是一条语法准确的语句。它的寄义是:先声明变量a
,对 [1, [2, 3]][3, 2, 1]
求值以后获得数组 [2, 3]
,对 [2, 3]
举行 (num) => num * num
映照操纵获得 [4, 9]
,将数组 [4, 9]
赋给变量 a
。
以 (
最先的语句,比方 IIFE ,也会致使顺序被 parser 误会。
(function fn() {
return fn;
})()
(function() {
console.log('我会显现在控制台吗?');
})()
它等价于
// 一条函数一连挪用语句
(function fn() {
return fn;
})()(function() {
console.log('我会显现在控制台吗?');
})() // => fn
以 /
最先的语句,通常是正则表达式放在语句肇端处(这类状况比较少见),也会致使顺序被 parser 误会。
var a = 2
/error/i.test('error')
它等价于
var a = 2 / error / i.test('error')
// => Uncaught ReferenceError: error is not defined
需要注重的是,虽然 var a = 2 / error / i.test('error')
会抛出 ReferenceError
非常,但它是一条没有语法错误 (SyntaxError) 的语句。换句话说,该语句在 parser 眼里是一条语法准确的语句,因而 parser 不会启用 ASI 机制。
语句肇端处的 +
和 -
也会致使源码被误会(越发少见)。
var num = 5
+new Date - new Date(2009, 10)
等价于
var num = 5 + new Date - new Date(2009, 10)
源码的企图被 parser 误会,有两个必要条件:
- parser 优先将行完毕符前后的标记流按一条语句剖析,这是 ECMAScript 规范的划定,一切 parser 必需要按此请求完成。
- 行完毕符以后的标记 (token) 有二义性,使得该标记与上条语句可以无缝对接,不致使语法错误。
实际上,有二义性的标记原本就不多,能致使源码企图被转变的标记数来数去就只有 [
、(
、/
、+
、-
这五个罢了。我们可以把它们明白成“软弱的标记”,在它们前面显式地加上防御性分号 (defensive semicolon) 来庇护其寄义不被转变。
限定发生式——备胎转正
前文说到,ASI 是一种备用挑选。然则在 ECMAScript 中,有几种特别语句是不许可行完毕符存在的。假如语句中有行完毕符,parser 会优先以为行完毕符示意的是语句的完毕,这在 ECMAScript 规范中称为限定发生式 (restricted production)。
浅显地说,在限定发生式中,parser 优先启用 ASI 机制。
一个典范限定发生式的例子是 return
语句。
function a() {
return
{};
}
a() // => undefined
根据平常剖析划定规矩,假如 ASI 是第二挑选,那末 parser 优先疏忽 \n
,该代码段应与下面的顺序无异:
function a() {
return {};
}
a() // => {} (empty object)
然则现实并不是如此,由于 ECMAScript 规范对正当的 return
语句做了以下限定:
ReturnStatement:
return [no LineTerminator here] Expression ;
return
语句中是不许可在 return
关键字以后涌现行完毕符的,所以上面的代码段着实等价于:
function a() {
return; // ReturnStatement
{} // BlockStatement
; // EmptyStatement
}
a() // => undefined
函数体内的代码被剖析为 return
语句、块语句、空语句三条零丁的语句。
规范划定的别的限定发生式有:
-
continue
语句 -
break
语句 -
throw
语句 - 箭头函数 (箭头左边不许可有行完毕符)
-
yield
表达式 - 后自增/自减表达式
这些状况都不许可有换行符存在。
a
++
b
被剖析为
a;
++b;
ES2015 规范给出了关于限定发生式的编程发起:
- A postfix
++
or --
operator should appear on the same line as its operand. (后自增运算符或后自减运算符应与它的操纵数处于统一行。) - An Expression in a
return
or throw
statement or an AssignmentExpression in a yield
expression should start on the same line as the return
, throw
, or yield
token. (return
或 throw
语句中的表达式以及 yield
表达式中的赋值表达式应与 return
、throw
、yield
这些关键字处于统一行。) - An IdentifierReference in a
break
or continue
statement should be on the same line as the break
or continue
token. (break
或 continue
语句中的标署名应与 break
或 continue
关键字处于统一行。)
言而总之,总而言之,ES2015 规范这一节就通知你一件事:在限定临盆式中别换行,换行就自动插进去分号。
for 轮回与空语句——永不运用的备胎
ASI 不适用于 for 轮回头部,即 parser 不会在这里自动插进去分号。
var a = ['once', 'a', 'rebound,', 'always', 'a', 'rebound.']
var msg = ''
for (var i = 0, len = a.length
i i++) {
msg += a[i] + ' '
}
console.log(msg)
好吧,或许你愿望 parser 在 a.length
背面和 i 背面自动为你插进去分号,补全这个 for 轮回语句,然则 parser 不会在 for 轮回的头部启用 ASI 机制。parser 起首尝试按一条语句剖析
(var i = 0, len = a.length \n i
这个标记流,发明它并不是一条正当语句后,就直接抛出了语法错误 Uncaught SyntaxError: Unexpected Identifier
,根本不尝试补全分号。
所以 for 轮回头部的分号必需要显式地写出:
var a = ['once', 'a', 'rebound,', 'always', 'a', 'rebound.']
var msg = ''
for (var i = 0, len = a.length;
i i++) {
msg += a[i] + ' '
}
console.log(msg)
// => 'once a rebound, always a rebound.' (一朝为备胎,永远为备胎)
类似地,有特别寄义的空语句也不可以省略分号:
function infiniteLoop() {
while('a rebound is a rebound is a rebound')
}
此段代码是不正当的语句,parser 会抛出语法错误 Uncaught SyntaxError: Unexpected token }
。这是由于轮回体中作为空语句而存在的 ;
不能省略。
// legal syntax
function infiniteLoop() {
while('a rebound is a rebound is a rebound');
}
// it is true that a rebound is a rebound is a rebound (备胎就是备胎,这是真谛。)
总结
ASI 机制的存在为 Javascript 顺序员供应了一种挑选:你可以省略源码中的绝大部分 ;
而不影响顺序的准确剖析。与 IT 业界的 “Vim 和 Emacs 哪一个是更好的编辑器” 一样,Javascript 社区隔一段时间就会涌现“该不该写分号”如许的看法之争。本文并不是想证实哪一种看法更好,而是关注 ASI 机制自身的一些风趣现实。即便是坚决的无分号党,也不能不认可,有些分号是不能省略的。这些不能省略的分号有:
- for 轮回头部的分号
- 作为空语句存在的分号
- 以 5 个“软弱标记”开首的语句之前的分号 (严厉来说,此处的分号不是必需的;由于除了运用分号,还可以用种种 hack 要领,比方 void)
而关于坚决的分号党,有一个现实也不能不认可,那就是你的顺序中很可能有 99% 的分号都是过剩的!假如你想尝试一下不写分号,可以根据下面的步骤:
- 删掉你一切语句结尾处的分号
- 假如你的语句开首是
[
或 (
,在它前面加一个分号。Over!
相干材料
- Effective Javascript: 68 Specific Ways to Harness the Power of Javascript. Item. 6
- ES2015 Spec Section 11.9.1: Rules of Automatic Semicolon Insertion
- An Open Letters to Javascript Leaders Regarding Semicolons
- Javascript Semicolon Insertion. Everything you need to know
- A Bit of Advice for the Javascript Semicolon Haters
- Automatic semicolon insertion in Javascript