JavaScript 自动分号插入 (ASI) 的规则是什么?

IT技术 javascript automatic-semicolon-insertion
2020-12-25 23:32:22

好吧,首先我应该问这是否与浏览器有关。

我已经读过,如果找到一个无效的标记,但该部分代码在该无效标记之前一直有效,如果它前面有一个换行符,则会在该标记之前插入一个分号。

但是,引用的由分号插入引起的错误的常见示例是:

return
  _a+b;

..这似乎不遵循此规则,因为 _a 将是一个有效的标记。

另一方面,分解调用链按预期工作:

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

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

6个回答

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

  • 空语句
  • var 陈述
  • 表达式语句
  • do-while 陈述
  • continue 陈述
  • break 陈述
  • return 陈述
  • throw 陈述

ASI的具体规则,在规范§11.9.1自动分号插入规则中描述

描述了三种情况:

  1. 当遇到语法不允许的违规标记时,在以下情况下会在其前插入分号:
  • 该令牌与前一个令牌至少相隔一个LineTerminator
  • 令牌是 }

例如

    { 1
    2 } 3

转化为

    { 1
    ;2 ;} 3;

NumericLiteral 1满足所述第一条件,令牌是行终止如下。
2满足第二条件,令牌是以下}

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

例如

    a = b
    ++c

转化为:

    a = b;
    ++c;
  1. 这种情况发生在语法的某些产生式允许标记,但产生式是受限产生式时,分号自动插入受限标记之前。

限制生产:

    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";
它所说的“输入流”是指“一条线”吗?“输入令牌流”使它有点难以理解
2021-02-13 23:32:22
请注意 ASI 实际上不需要“插入分号”,只是为了终止引擎解析器中的语句......
2021-02-18 23:32:22
规范链接对其他人有效吗?它把我带到一个几乎空白的页面,上面有一个死链接。
2021-02-18 23:32:22
#1:语法不允许的标记通常不是行终止符,是吗(除非您指的是#3 中的受限产生式)?它认为你应该省略括号。#2++c为了清楚起见,示例不应该只显示后面的插入吗?
2021-03-01 23:32:22
请解释一下,根据这些规则,下面太极者无极而生的“a [LineBreak] = [LineBreak] 3”的例子仍然有效
2021-03-07 23:32:22

我不太理解规范中的这 3 条规则——希望有一些更简单的英语——但这是我从 JavaScript 收集的:权威指南,第 6 版,大卫弗拉纳根,奥莱利,2011 年:

引用:

JavaScript 不会将每个换行符都视为分号:它通常仅在无法解析没有分号的代码时才将换行符视为分号。

另一个引用:对于代码

var a
a
=
3 console.log(a)

JavaScript 不会将第二个换行符视为分号,因为它可以继续解析更长的语句 a = 3;

和:

JavaScript 将换行符解释为分号的一般规则的两个例外,当它无法将第二行解析为第一行语句的延续时。第一个异常涉及 return、break 和 continue 语句

... 如果在这些单词中的任何一个之后出现换行符... JavaScript 将始终将该换行符解释为分号。

... 第二个例外涉及 ++ 和 −− 运算符 ... 如果您想将这些运算符中的任何一个用作后缀运算符,它们必须与它们所应用的表达式出现在同一行。否则,换行符将被视为分号,++ 或 -- 将被解析为应用于后续代码的前缀运算符。考虑这个代码,例如:

x 
++ 
y

它被解析为x; ++y;,而不是x++; y

所以我认为简化它,这意味着:

在一般情况下,JavaScript的把它当作码延续,只要它是有道理的-除了两种情况:(1)经过一些关键字时returnbreakcontinue,和(2)如果看到++或者--在新的一行,则反而会加重该;在前一行的末尾。

关于“只要有意义就将其视为代码的延续”的部分让人感觉像是正则表达式的贪婪匹配。

如上所述,这意味着对于return换行符,JavaScript 解释器将插入一个;

(再次引用:如果在这些单词中的任何一个之后出现换行符 [例如return] ... JavaScript 将始终将该换行符解释为分号)

由于这个原因,经典的例子

return
{ 
  foo: 1
}

不会按预期工作,因为 JavaScript 解释器会将其视为:

return;   // returning nothing
{
  foo: 1
}

在 之后必须没有换行符return

return { 
  foo: 1
}

使其正常工作。;如果您要遵循;在任何语句之后使用 a 的规则,您可以自己插入 a

return { 
  foo: 1
};

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

7.9.1 自动分号插入规则

分号插入的三个基本规则:

  1. 当程序从左到右解析时,遇到任何语法产生式都不允许的记号(称为违规记号),如果出现以下一项或多项情况,则会在违规记号前自动插入分号条件为真:
    • 违规令牌与前一个令牌至少隔开一个LineTerminator
    • 违规令牌是}
  2. 当程序从左到右解析时,遇到令牌输入流的末尾并且解析器无法将输入令牌流解析为单个完整的 ECMAScript 时Program,则自动在末尾插入分号输入流。
  3. 当程序从左到右解析时,遇到语法的某些产生式允许的标记,但该产生式是受限产生式并且该标记将是紧跟注释之后的终结符或非终结符的第一个标记“ [no LineTerminatorhere] ”在受限产生式中(因此这种标记被称为受限标记),并且受限标记与前一个标记至少被一个LineTerminator分隔,然后在受限标记之前自动插入一个分号。

但是,前面的规则还有一个额外的覆盖条件:如果分号随后会被解析为空语句,或者该分号将成为for语句标题中的两个分号之一,则永远不会自动插入分号(参见 12.6 .3)。

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

    var srcRecords = src.records
        srcIds = [];

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

我认为在每一行上输入var( let, const)的清晰度超过了输入它所需的几分之一秒。
2021-02-08 23:32:22
这就是我使用 jsLint 的原因
2021-02-11 23:32:22
JsHint/Lint 就在您的代码编辑器中,立即响应:)
2021-02-22 23:32:22
@balupton 当忘记结束该行的逗号时,会自动插入一个分号。与规则相反,它更像是一个“陷阱”。
2021-02-26 23:32:22
我认为 balupton 是正确的,如果你写:var srcRecords = src.records srcIds = [];在一行中忘记逗号,或者你写“return a && b”并且什么都不忘记……但是在 a 之前的换行符会在 return 后插入一个自动分号,这是由 ASI 规则定义的...
2021-03-03 23:32:22

我发现的 JavaScript自动分号插入的最上下文描述来自一本关于Crafting Interpreters的书

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

他继续将其描述为代码气味

如果我深入了解它是如何工作的完整细节,那么这个设计说明将变成设计谩骂,更不用说这是一个坏主意的所有各种方式。一团糟。JavaScript 是我所知道的唯一一种语言,其中许多样式指南要求在每个语句后显式分号,即使该语言理论上允许您省略它们。