首先我应该问一下这是否依赖于浏览器。

我曾经读到过,如果发现了一个无效的令牌,但代码段在该无效令牌之前是有效的,如果令牌之前有换行符,则在令牌之前插入一个分号。

然而,常见的由分号插入引起的错误的例子是:

return
  _a+b;

..这似乎不符合这个规则,因为_a将是一个有效的令牌。

另一方面,打破调用链可以正常工作:

$('#myButton')
  .click(function(){alert("Hello!")});

有人对规则有更深入的描述吗?


当前回答

补充一点,

const foo = function(){ return "foo" } //this doesn't add a semicolon here.
(function (){
    console.log("aa");
})()

请看,使用立即调用的函数表达式(IIFE)

其他回答

首先,你应该知道哪些语句会受到自动分号插入(为简洁起见,也称为ASI)的影响:

空语句 var声明 表达式语句 延伸的声明 继续声明 break语句 返回语句 把语句

ASI的具体规则见§11.9.1自动分号插入规则

本文描述了三个案例:

当遇到语法不允许的违规标记时,在它前面插入分号,如果:

令牌与前一个令牌之间至少用一个LineTerminator分隔。 令牌是}

例如:

    { 1
    2 } 3

转化为

    { 1
    ;2 ;} 3;

NumericLiteral 1满足第一个条件,下面的标记是行结束符。 2满足第二个条件,下面的令牌是}。

当遇到令牌输入流的末尾时,解析器无法将输入令牌流解析为一个完整的程序,则在输入流的末尾自动插入一个分号。

例如:

    a = b
    ++c

转换为:

    a = b;
    ++c;

这种情况发生在文法的某些产物允许一个令牌,但该产物是受限制的产物时,在受限制的令牌之前会自动插入一个分号。

受限制的产品:

    UpdateExpression :
        LeftHandSideExpression [no LineTerminator here] ++
        LeftHandSideExpression [no LineTerminator here] --
    
    ContinueStatement :
        continue ;
        continue [no LineTerminator here] LabelIdentifier ;
    
    BreakStatement :
        break ;
        break [no LineTerminator here] LabelIdentifier ;
    
    ReturnStatement :
        return ;
        return [no LineTerminator here] Expression ;
    
    ThrowStatement :
        throw [no LineTerminator here] Expression ; 

    ArrowFunction :
        ArrowParameters [no LineTerminator here] => ConciseBody

    YieldExpression :
        yield [no LineTerminator here] * AssignmentExpression
        yield [no LineTerminator here] AssignmentExpression

经典的例子,使用ReturnStatement:

    return 
      "something";

转化为

    return;
      "something";

关于分号插入和var语句,注意在使用var但跨越多行时忘记使用逗号。有人昨天在我的代码中发现了这个:

    var srcRecords = src.records
        srcIds = [];

它运行了,但结果是srcid声明/赋值是全局的,因为在前一行中带有var的局部声明不再应用,因为由于自动插入分号,该语句被认为已完成。

直接来自ECMA-262,第五版ECMAScript规范:

7.9.1 Rules of Automatic Semicolon Insertion There are three basic rules of semicolon insertion: When, as the program is parsed from left to right, a token (called the offending token) is encountered that is not allowed by any production of the grammar, then a semicolon is automatically inserted before the offending token if one or more of the following conditions is true: The offending token is separated from the previous token by at least one LineTerminator. The offending token is }. When, as the program is parsed from left to right, the end of the input stream of tokens is encountered and the parser is unable to parse the input token stream as a single complete ECMAScript Program, then a semicolon is automatically inserted at the end of the input stream. When, as the program is parsed from left to right, a token is encountered that is allowed by some production of the grammar, but the production is a restricted production and the token would be the first token for a terminal or nonterminal immediately following the annotation "[no LineTerminator here]" within the restricted production (and therefore such a token is called a restricted token), and the restricted token is separated from the previous token by at least one LineTerminator, then a semicolon is automatically inserted before the restricted token. However, there is an additional overriding condition on the preceding rules: a semicolon is never inserted automatically if the semicolon would then be parsed as an empty statement or if that semicolon would become one of the two semicolons in the header of a for statement (see 12.6.3).

JavaScript中的大多数语句和声明必须以分号结束,然而,为了程序员的方便(更少的输入,风格偏好,更少的代码噪音,更低的进入壁垒),在一些源文本位置可以省略分号,运行时根据规范中设置的一组规则自动插入分号。

覆盖规则:如果分号将被解析为空语句,或者该分号将成为for语句头中的两个分号之一,则永远不会自动插入分号。

规则1

如果JavaScript解析器遇到一个令牌(如果不存在分号,两个令牌都不允许),那么将自动插入分号,并且该令牌由一个或多个行终止符(例如。do-while循环的换行符)、右括号}或最后的圆括号())。

换句话说:对于可运行的程序,无论如何都需要终止语句的源文本位置,如果省略语句终止符(;),则会自动插入。这条规则是ASI的核心。

规则2

如果源文本不是有效的脚本或模块,则将在程序末尾插入分号。换句话说:程序员可以省略程序中的最后一个分号。

规则3

如果遇到一个令牌,如果分号不存在,则通常允许该令牌存在,但存在于几个特殊源文本位置之一(受限制的结果)中,这些位置为避免歧义而显式禁止在其中使用行终止符,则将自动插入分号。

禁止在其中使用行终止符的受限产品如下:

在后缀++和后缀——之前(因此换行符后的一元加/减操作符将绑定到下面(不是前面)语句,作为前缀操作符) 继续之后,中断,投掷,返回,屈服 后箭头函数参数列表,和 在异步函数声明和表达式、生成器函数声明和表达式和方法以及异步箭头函数中的async关键字之后


该规范包含了全部细节,并给出了以下实用建议:

The resulting practical advice to ECMAScript programmers is: A postfix ++ or -- operator should be 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. A LabelIdentifier in a break or continue statement should be on the same line as the break or continue token. The end of an arrow function's parameter(s) and its => should be on the same line. The async token preceding an asynchronous function or method should be on the same line as the immediately following token.

这是网上关于这个主题最好的文章。

ASI的例子

以“(”开始一行

开括号字符有多重含义。它可以描述表达式,也可以指示调用(当与右括号配对时)。

例如,下面会抛出“Uncaught TypeError: console.log(…)is not a function”,因为运行时试图调用console.log('bar')的返回值:

let a = 'foo'
console.log('bar')
(a = 'bam') 

一个解决方案是,如果你通常省略分号,包括一个分号,使你的意图明确:

let a = 'foo'
console.log('bar')
;(a = 'bam') // note semicolon at start of line

以“[”开始一行

左括号字符([)有多重含义。它可以指示对象属性访问,也可以指示数组的文字声明(当与右括号配对时),也可以指示数组析构。

例如,下面会抛出“Uncaught TypeError: Cannot set properties of undefined (setting 'foo')”,因为运行时试图在console.log('bar')的响应上设置名为'foo'的属性的值:

let a = 'foo'
console.log('bar')
[a] = ['bam']

一个解决方案是,如果你通常省略分号,包括一个分号,使你的意图明确:

let a = 'foo'
console.log('bar')
;[a] = ['bam'] // note semicolon at start of line

我找到的关于JavaScript自动分号插入的最贴切的描述来自一本关于制作解释器的书。

JavaScript的“自动分号插入”规则是一个奇怪的规则。其他语言认为大多数换行符是有意义的,在多行语句中只有少数换行符应该被忽略,而JS则相反。除非遇到解析错误,否则它将所有换行符视为无意义的空格。如果是,则返回并尝试将前面的换行符转换为分号,以获得语法上有效的内容。

他继续描述它,就像你对气味编码一样。

如果我详细说明这是如何运作的,这篇设计说明就会变成一篇设计攻略,更不用说这是一个坏主意的各种方式了。真是一团糟。JavaScript是我所知道的唯一一种语言,许多风格指南要求在每个语句后显式地使用分号,尽管理论上该语言允许您省略分号。