Skip to content

Latest commit

 

History

History
732 lines (470 loc) · 37.8 KB

Lexical-Conventions.md

File metadata and controls

732 lines (470 loc) · 37.8 KB

ECMAScript 程序的源代码文本首先转换成一个由 Token行终止符注释空白字符 组成的输入元素序列;从左到右扫描源代码文本,反复获取尽可能长的字符序列来作为下一个输入元素。

词法有两个目标符。InputElementDiv 目标符用于允许以除法(/)或除赋值(/=)运算符开始的句法上下文中。InputElementRegExp 目标符用于其他句法文法上下文中。

注:不存在既允许以除法或除赋值运算符开头又允许以 RegularExpressionLiteral 开头的句法上下文。这一点不会受分号插入的影响(见7.9节);如下示例:

a = b
/hi/g.exec(c).map(d);

其中,LineTerminator 之后的第一个非空白、非注释字符是斜杠(/),并且该句法上下文允许除法或除赋值运算符,所以不会在 LineTerminator 位置插入分号。换言之,上面的例子解释为:

a = b / hi / g.exec(c).map(d);

语法

 InputElementDiv ::
   WhiteSpace
   LineTerminator
   Comment
   Token
   DivPunctuator

 InputElementRegExp ::
   WhiteSpace
   LineTerminator
   Comment
   Token
   RegularExpressionLiteral

Unicode 格式控制字符

Unicode 格式控制字符(例如,Unicode 字符数据库中 Cf 分类中的字符,如“左至右符号”或“右至左符号”)用来控制被更高层级协议(如标记语言)忽略的某个范围内文本格式化的控制代码。

允许在源代码文本中出现控制字符非常有利于编辑和显示。所有格式控制字符均可用于注释、字符串字面量、正则表达式字面量。

<ZWNJ><ZWJ> 是格式控制字符,可用于在某些语言中形成单词或段落时产生必要的差异。在 ECMAScript 源代码文本中,<ZWNJ><ZWJ> 也可用于首字符之后的标识符。

<BOM> 是一个格式控制字符,主要用于文本的开头,将文本标记为 Unicode,且允许检查文本编码和字节顺序。为此目的,<BOM> 字符有时也可显示在文本的开始位置之后,例如作为一个合并文件的结果。<BOM> 字符还可用作空白字符(见7.2节)

表1 总结了一些在注释、字符串字面量、正则表达式字面量之外被特殊对待的格式控制字符。

代码单元值 名称 正式名称 用法
\u200C 零宽度非连接器 <ZWNJ> IdentifierPart
\u200D 零宽度连接器 <ZWJ> IdentifierPart
\uFEFF 字节顺序标记 <BOM> Whitespace

空白字符

空白字符用来改善源文本的可读性和分割 Token(不可分割的词法单位),此外就无关紧要。空白字符既可以出现在任意两个 Token 之间,又可以出现在输入的开始或结束位置,还可以出现在 StringLiteralRegularExpressionLiteral(在这里它们表示组成字面量的字符)抑或 Comment 中,但是不能出现的其他任何 Token 内。

表2 列出了 ECMAScript 空白字符。

代码单元值 名称 正式名称
\u0009 制表符 <TAB>
\u000B 垂直制表符 <VT>
\u000C 换页符 <FF>
\u0020 空格符 <SP>
\u00A0 非中断空格符 <NBSP>
\uFEFF 字节顺序标记 <BOM>
其他 Zs 类字符 其他 Unicode 空白分隔符 <USP>

ECMAScript 实现必须识别出 Unicode 3.0 中定义的所有空白字符。后续版本的 Unicode 标准可能定义其他空白字符。ECMAScript 实现可以识别出更高版本的 Unicode 标准中的空白字符。

语法:

 WhiteSpace ::
   <TAB>
   <VT>
   <FF>
   <SP>
   <NBSP>
   <BOM>
   <USP>

行终止符

像空白字符一样,行终止字符用于改善源文本的可读性和分割 Token(不可分割的词法单位)。然而,和空白字符不同的是,行终止符对句法的行为有一定的影响。通常,行终止符可以出现在任何两个 Token 之间;但也有少数地方,句法禁止这样做。行终止符也影响自动插入分号的过程(7.9)。行终止符不能出现在 StringLiteral 之外的任何 Token 内。行终止符只能作为 LineContinuation 的一部分出现在 StringLiteral 内。

行终止符可以出现在 MultiLineComment 内,但不能出现在 SingleLineComment 内。

正则表达式 \s 类所匹配的空白字符集中包含行终止符。

表3 列出了 ECMAScript 的行终止字符。

代码单元值 名称 正式名称
\u000A 换行符 <LF>
\u000D 回车符 <CR>
\u2028 行分隔符 <LS>
\u2029 段落分割符 <PS>

只有 表3 中的字符才被视为行终止符。其他新行或折行字符被视为空白,但不作为行终止符。字符序列 <CR><LF> 常作为一个行终止符。计算行数时它应该被视为一个字符。

语法:

 LineTerminator ::
   <LF>
   <CR>
   <LS>
   <PS>

 LineTerminatorSequence ::
   <LF>
   <CR>[lookahead ? <LF>]
   <LS>
   <PS>
   <CR><LF>

注释

注释可单行或多行。多行注释不能嵌套。

因为单行注释可以包含除了 LineTerminator 字符之外的任何字符,又因为有个常规:一个 Token 总是尽可能匹配更长,所以一个单行注释总是包含从 // 到行终止符之间的所有字符。然而,在该行末尾的 LineTerminator 不是单行注释的一部分,它被词法识别成句法输入元素流的一部分。这一点非常重要,因为这意味着无论是否存在单行注释都不会影响自动分号插入过程(见 7.9)。

像空白字符一样,注释会被句法简单丢弃,除了 MultiLineComment 包含行终止符字符的情况,这种情况下整个注释会被当作一个 LineTerminator 提供给句法文法解析。

语法:

 Comment ::
   MultiLineComment
   SingleLineComment

 MultiLineComment ::
   /* MultiLineCommentCharsopt */

 MultiLineCommentChars ::
   MultiLineNotAsteriskChar MultiLineCommentCharsopt
   * PostAsteriskCommentCharsopt

 PostAsteriskCommentChars ::
   MultiLineNotForwardSlashOrAsteriskChar MultiLineCommentCharsopt
   * PostAsteriskCommentCharsopt

 MultiLineNotAsteriskChar ::
   SourceCharacter but not *

 MultiLineNotForwardSlashOrAsteriskChar ::
   SourceCharacter but not / or *

 SingleLineComment ::
   // SingleLineCommentCharsopt

 SingleLineCommentChars ::
   SingleLineCommentChar SingleLineCommentCharopt

 SingleLineCommentChar ::
   SourceCharacter but not LineTerminator

Token

语法:

 Token ::
   IdentifierName
   Punctuator
   NumericLiteral
   StringLiteral

注:DivPunctuatorRegularExpressionLiteral 产生式定义了 Token,但 Token 产生式并不包含它们。

标识符名和标识符

标识符名属于 TokenUnicode标准第5章 的“Identifiers”节给出的文法加入了一些小修改来解释它。** 是一个 ** 但不是一个 **。Unicode 标识符文法基于 Unicode 标准指出的 normative 和 informative 字符分类 。所有符合 ECMAScript 的实现必须能够正确处理 Unicode 标准 3.0 版本中指定的分类里的字符的分类。

本标准增加了个别字符:在 ** 的任何位置允许出现美元符($)和下划线(_)。

** 还允许出现 Unicode 转义序列,它们被 ** 的字符值计算成单个字符贡献给 (见 7.8.4)。 前面的 \ 不给 ** 贡献字符。** 不能提供单个字符给将要成为非法字符的 **。换句话说,如果一个 \ ** 序列被 ** 的字符值替换,结果必须仍是有效的包含与原 ** 精确相同字符序列的 **。本规范说明的所有标识符是根据它的实际字符,不管转义序列贡献特定字符与否。

根据 Unicode 标准两个规范的 相等,是说除非他们的代码单元序列准确相等,否则不同(换句话说,符合 ECMAScript 的实现只需要按位比较 值)。其目的是为了传入编译器之前就把源代码文本转换为正规形式 C。

ECMAScript 实现可以识别后续版本 Unicode 标准定义的标识符字符。如果考虑可移植性,程序员应该只采用 Unicode 3.0 中定义的标识符字符。

语法:

 Identifier ::     but not 

 IdentifierName ::    **     

 IdentifierStart ::    **    $    _    \ **

  IdentifierPart ::    **    **    **    **        

 UnicodeLetter    any character in the Unicode categories     “Uppercase letter (Lu)”, “Lowercase letter (Ll)”,     “Titlecase letter (Lt)”, “Modifier letter (Lm)”,    “Other letter (Lo)”,or “Letter number (Nl)”.

 UnicodeCombiningMark    any character in the Unicode categories “Non-spacing mark (Mn)    or “Combining spacing mark (Mc)

 UnicodeDigit    any character in the Unicode category “Decimal number (Nd)

 UnicodeConnectorPunctuation    any character in the Unicode category “Connector punctuation (Pc)

保留字

保留字不能作为 ** 的 **。

语法

 ReservedWord ::    **    **    **    **

关键词

下列 Token 是 ECMAScript 的关键词,不能用作 ECMAScript 程序的 **。

语法

 Keyword :: one of     break      do         instanceof        typeof     case       else       new               var     catch      finally    return            void     continue   for        switch            while     debugger   function   this              with     default    if         throw             delete     in         try

未来保留字

下列词被用作建议扩展关键字,因此保留,以便未来可能采用这些扩展。

语法

 FutureReservedWord :: one of     class      enum       extends       super     const      export     import

当下列 Token 出现在严格模式代码 (strict mode code )(见 10.1.1)里,将被当成是 **。任意这些 Token 出现在任意上下文中的严格模式代码中,如果 ** 出现的位置会产生错误,那么必须抛出对应的异常:

 implements      let         private       public      yield  interface       package     protected     static

标点符号

语法

 Punctuator :: one of    {       }       (       )       [       ]    .       ;       ,       <       >       <=    >=      ==      !=      ===     !==    +       -       *       %       ++      --    <<      >>      >>>     &       |       ^    !       ~       &&      ||      ?       :    =       +=      -=      *=      %=      <<=    >>=     >>>=    &=      |=      ^=

 DivPunctuator :: one of    /       /=

字面量

语法

 Literal ::    **    **    **         

空值字面量

语法:

 NullLiteral ::    null

语义:

空值字面量的值 null,是 Null类型 的唯一值。

布尔值字面量

语法:

 BooleanLiteral ::    true    false

语义:

布尔值字面量的值 true 是个 Boolean类型 值 ,即 true。 布尔值字面量的值 false 是个 Boolean类型 值 ,即 false

数值字面量

语法:

 NumericLiteral ::    **    **

 DecimalLiteral ::     .  **    .       

 DecimalIntegerLiteral ::    0     

 DecimalDigits ::    **     

 DecimalDigit :: one of    0 1 2 3 4 5 6 7 8 9

 NonZeroDigit :: one of    1 2 3 4 5 6 7 8 9

 ExponentPart ::     

 ExponentIndicator :: one of    e E

 SignedInteger ::    **    + **    - **

 HexIntegerLiteral ::    0x **    0X **     

 HexDigit :: one of    0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F

源字符中的 ** 后面不允许紧跟着 ** 或 **。

语义:

一个数值字面量代表一个 Number类型 的值。此值取决于两个步骤:第一,由字面量得出的数学值);第二,这个数学值按照后面描述的规则舍入。

  • ** :: ** 的数学值是 ** 的数学值。

  • ** :: ** 的数学值是 ** 的数学值。

  • ** :: ** . 的数学值是 ** 的数学值。

  • ** :: ** . ** 的数学值是 ** 的数学值加上 (** 的数学值乘 10-n), 这里的 n 是 ** 的字符个数。

  • ** :: ** . ** 的数学值是 ** 的数学值乘 10e, 这里的 e 是 ** 的数学值。

  • ** :: ** . ** ** 的数学值是 (** 的数学值加 (** 的数学值乘 10-n)) 乘 10e, 这里的 n 是 ** 的字符个数,e 是 ** 的数学值。

  • ** :: . ** 的数学值是 ** 的数学值乘 10-n, 这里的 n 是 ** 的字符个数。

  • ** :: . ** ** 的数学值是 ** 的数学值乘 10e-n, 这里的 n 是 ** 的字符个数,e 是 ** 的数学值。

  • ** :: ** 的数学值是 ** 的数学值。

  • ** :: ** ** 的数学值是 ** 的数学值乘 10e, 这里的 e 是 ** 的数学值。

  • ** :: 0 的数学值是 0

  • ** :: ** ** 的数学值是 (** 的数学值乘 10n) 加 ** 的数学值, 这里的 n 是 ** 的字符个数。

  • ** :: ** 的数学值是 ** 的数学值。

  • ** :: ** ** 的数学值是 (** 的数学值乘 10) 加 ** 的数学值。

  • ** :: ExponentIndicator ** 的数学值是 ** 的数学值。

  • ** :: ** 的数学值是 ** 的数学值。

  • ** :: + ** 的数学值是 ** 的数学值。

  • ** :: - ** 的数学值是 ** 的数学值取负。

  • ** :: 0 或 ** :: 0 的数学值是 0

  • ** :: 1 或 ** :: 1 或 ** :: 1 的数学值是 1

  • ** :: 2 或 ** :: 2 或 ** :: 2 的数学值是 2

  • ** :: 3 或 ** :: 3 或 ** :: 3 的数学值是 3

  • ** :: 4 或 ** :: 4 或 ** :: 4 的数学值是 4

  • ** :: 5 或 ** :: 5 或 ** :: 5 的数学值是 5

  • ** :: 6 或 ** :: 6 或 ** :: 6 的数学值是 6

  • ** :: 7 或 ** :: 7 或 ** :: 7 的数学值是 7

  • ** :: 8 或 ** :: 8 或 ** :: 8 的数学值是 8

  • ** :: 9 或 ** :: 9 或 ** :: 9 的数学值是 9

  • ** :: a 或 ** :: A 的数学值是 10

  • ** :: b 或 ** :: B 的数学值是 11

  • ** :: c 或 ** :: C 的数学值是 12

  • ** :: d 或 ** :: D 的数学值是 13

  • ** :: e 或 ** :: E 的数学值是 14

  • ** :: f 或 ** :: F 的数学值是 15

  • ** :: 0x ** 的数学值是 ** 的数学值。

  • ** :: 0X ** 的数学值是 ** 的数学值。

  • ** :: ** ** 的数学值是 (** 的数学值乘 16) 加 ** 的数学值。

数值字面量的确切数学值一旦被确定,它就会舍入成 Number类型 的值。如果数学值是 0,那么舍入值是 +0;否则,舍入值必须是数学值对应的数字值,除非此字面量是有效数字超过20位的 **,这种情况下,数字值的产生方式可以是下面两种方式中的一种:一,将20位后的每个有效数字0 替换后产生的数学值;二,将20位后的每个有效数字0 替换,并且递增第20位有效数字位置的字面量值所产生的数学值 。有效数字必须满足这么几个条件,首先它不能是 ** 的一部分,并且

  • 它不是 0;或

  • 它的左侧是非零数字,它的右侧是不在 ** 的非零数字。

符合标准的实现,在处理严格模式代码时,按照 B.1.1 的描述,不得扩展 ** 包含 OctalIntegerLiteral 的语法。

字符串字面量

字符串字面量是在闭合的单引号或双引号中的零个或多个字符。每个字符都可以用一个转义序列代表。除了闭合用的引号字符反斜杠回车符行分隔符段落分隔符换行符之外的所有字符都可以直接出现的字符串字面量里。任何字符都可以通过转移序列的形式出现。

语法

 StringLiteral ::    "  "**    '  '**

 DoubleStringCharacters ::     

 SingleStringCharacters ::     

 DoubleStringCharacter ::    SourceCharacter but not " or \ or **    \ **    **

 SingleStringCharacter ::    SourceCharacter but not ' or \ or **    \ **    **

 LineContinuation ::    \ **

 EscapeSequence ::    **    0 [lookahead ? ]        **

 CharacterEscapeSequence ::    **    **

 SingleEscapeCharacter :: one of    ' " \ b f n r t v

 NonEscapeCharacter ::    SourceCharacter but not  or 

 EscapeCharacter ::    **    **    x    u

 HexEscapeSequence ::    x  

 UnicodeEscapeSequence ::    u    

7.8.3 给出了 ** 非终结符的定义。第6章 定义了 SourceCharacter

语义

一个字符串字面量代表一个 String类型 的值。字面量的字符串值由字符串字面量各部分贡献的字符值描述。作为这一过程的一部分,字符字面量里的某些字符字符会被解释成包含数学值,如 7.8.3 和下面描述的。

  • ** :: "" 的字符串值是空字符序列。

  • ** :: '' 的字符串值是空字符序列。

  • ** :: " ** " 的字符串值是 ** 的字符串值。

  • ** :: ' ** ' 的字符串值是 ** 的字符串值。

  • ** :: ** 的字符串值是包含一个字符的序列,此字符的字符值是 ** 的字符值。

  • ** :: ** ** 的字符串值是 (** 的字符值后面跟着 ** 的字符串值里所有字符的)序列。

  • ** :: ** 的字符串值是包含一个字符的序列,此字符的字符值是 ** 的字符值。

  • ** :: ** ** 的字符串值是(** 的字符值后面跟着 ** 的字符串值里所有字符的)序列。

  • ** :: \ ** 的字符串值是空字符序列。

  • ** :: SourceCharacter but not " or \ or ** 的字符值是 SourceCharacter 字符自身。

  • ** :: \ ** 的字符值是 ** 的字符值。

  • ** :: ** 的字符值是空字符序列。

  • ** :: SourceCharacter but not ' or \ or **的字符值是 SourceCharacter 字符自身。

  • ** :: \ ** 的字符值是 ** 的字符值。

  • ** :: ** 的字符值是空字符序列。

  • ** :: ** 的字符值是 ** 的字符值。

  • ** :: 0 [lookahead ? **] 的字符值是 字符(Unicode 值 0000)。

  • ** :: ** 的字符值是 ** 的字符值。

  • ** :: ** 的字符值是 ** 的字符值。

  • ** :: ** 的字符值是 表4 里的 ** 确定的代码单元值字符:

转义序列 代码单元值 名称 符号
\b \u0008 退格符
\t \u0009 水平制表符
\n \u000A 换行符
\v \u000B 垂直制表符
\f \u000C 换页符
\r \u000D 回车符
\" \u0022 双引号 "
\' \u0027 单引号 '
\|\u005C 反斜杠 |
  • ** :: ** 的字符值是 ** 的字符值。

  • ** :: SourceCharacter but not ** or ** 的字符值是 SourceCharacter 字符自身。

  • ** :: x ** ** 的字符值是 ((16 乘第一个 ** 的数学值) 加第二个 ** 的数学值) 代码单元确定的字符。

  • ** :: u ** ** ** ** 的字符值是 (4096 乘第一个 ** 的数学值) 加 (256 乘第二个 ** 的数学值) 加 (16 乘第三个 ** 的数学值) 加 ( 第四个 ** 的数学值) 代码单元确定的字符。

符合标准的实现,在处理严格模式代码时,按照 B.1.2 的描述,不得扩展 ** 包含 OctalEscapeSequence 的语法。

正则表达式字面量

正则表达式字面量是一个输入元素,它在每次被解析执行时候都会转换成一个 RegExp对象 的。当程序中的两个正则表达式字面量解释执行为正则表达式对象后就不能用 === 来比较它们是否相等,即使它们所包含的内容相同。RegExp对象 也可以在运行时使用 new RegExp 或以函数方式调用 RegExp 构造器来创建。

下面的产生式描述了正则表达式字面量的语法,输入元素扫描器还用它搜索正则表达式字面量的结束位置。** 和 ** 包含的字符组成的字符串会直接传递给正则表达式构造器,在那里用更严格文法进行解析。一个实现可以扩展正则表达式构造器的文法。但它不能扩展 ** 和 ** 产生式或使用这些产生式的产生式。

语法

 RegularExpressionLiteral ::    /  / 

 RegularExpressionBody ::     

 ''RegularExpressionChars ::    [empty]     

 RegularExpressionFirstChar ::     but not * or \ or / or [**    **    **

 RegularExpressionChar ::     but not \ or / or [**    **    **

 RegularExpressionBackslashSequence ::    \ **

 RegularExpressionNonTerminator ::    SourceCharacter but not **

 RegularExpressionClass ::    [  ]**

 RegularExpressionClassChars ::    [empty]     

 RegularExpressionClassChar ::     but not ] or \**    **

 RegularExpressionFlags ::    [empty]     

语义

正则表达式字面量会解释执行为一个 Object类型 值,它是标准内置构造器 RegExp 的一个实例。此值取决于两个步骤:首先,展开组成正则表达式产生式 ** 和 ** 的字符,将其以未解析形式分别存成两个字符串 PatternFlags。然后,在每次解释执行字面量时创建新对象,仿佛使用 new RegExp(Pattern,Flags) 一样,这里的 RegExp 是标准内置构造器名。新构造的对象将成为 ** 的值。如果调用 new RegExp 会产生 15.10.4.1 指定的错误,那么必须把错误当作是早期错误 ( 见第16章)。

自动分号插入

必须用分号终止某些 ECMAScript 语句(空语句变量语句表达式语句do-while 语句continue 语句break 语句return 语句throw 语句)。这些分号总是明确地显示在源文本里。然而,为了方便起见,某些情况下这些分号可以在源文本里省略。描述这种情况会说:这种情况下给源代码的 Token 流自动插入分号。

自动分号插入规则

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

  1. 从左到右解析程序,当遇到一个不符合任何文法产生式的 Token(叫做违规Token),那么只要满足下面条件之一就在 违规Token 前面自动插入分号。

  2. 从左到右解析程序,Token 输入流已经结束,当解析器无法将输入 Token 流解析成单个完整 ECMAScript Program,那么就在输入流的结束位置自动插入分号。

  3. 从左到右解析程序,遇到一个某些文法产生式允许的 Token,但是此产生式是受限产生式,受限产生式的里紧跟在 [no LineTerminator here] 后的第一个终结符或非终结符的 Token 叫做受限的 Token,当至少一个 LineTerminator 分割了受限的 Token 和前一个 Token,那么就在受限 Token 前面自动插入分号。

然而,上述规则有一个附加的优先条件:如果插入分号后解析结果是空语句,或如果插入分号后它成为 for 语句 头部的两个分号之一,那么不会自动插入分号。

 PostfixExpression :    LeftHandSideExpression [no LineTerminator here++     LeftHandSideExpression [no LineTerminator here--

 ContinueStatement :    continue [no LineTerminator hereIdentifier ;

 BreakStatement :    break [no LineTerminator hereIdentifier ;

 ReturnStatement :    return [no LineTerminator hereExpression ;

 ThrowStatement :    throw [no LineTerminator hereExpression ;

这些受限产生式的实际效果如下:

当遇到一个被解析器作为后缀运算符来对待的 ++--,并且在 ++-- 与其前面的 Token 之间出现至少一个 **,这时分号会被自动插入到 ++-- 的前面。

当遇到 continuebreakreturnthrow 这些 Token,并且在下一个 Token 前面遇到 ** 时,在 continuebreakreturnthrow 后面自动插入一个分号。

这对 ECMAScript 程序员的实际影响是:

后缀运算符 ++-- 和它的操作数应该出现在同一行。

return 语句throw 语句Expression 开始位置应该和 returnthrow 这些 Token 本身处于同一行。

break 语句continue 语句Identifier 应该和 breakcontinue 这些 Token 本身处于同一行。

自动分号插入的例子

源代码:

 { 1 2 } 3

即使在自动分号插入规则下,它也不符合 ECMAScript 文法。做为对比,源代码:

 { 1  2 } 3

它还是不符合 ECMAScript 文法,但是它会被自动分号插入成为一下形式:

 { 1  ;2 ;} 3;

这符合 ECMAScript 文法。

源代码:

 for (a; b  )

不符合 ECMAScript 文法,并且不会被自动分号插入所更改,因为是 for 语句 头部需要分号。自动分号插入永远不会插入成 for 语句 头部的两个分号之一。

源代码:

 return  a + b

会被自动分号插入转换成以下形式:

 return;  a + b;

源代码:

 a = b  ++c

会被自动分号插入转换成以下形式:

 a = b;  ++c;

源代码:

 if (a > b)  else c = d

它不符合 ECMAScript 文法,else 这个 Token 前不会被自动插入分号,即使没有文法产生式适用这一位置,因为自动插入分号不该解析成空语句

源代码:

 a = b + c  (d + e).print()

它不会被自动分号插入改变,因为第二行开始位置的括号表达式可以解释成函数调用的参数列表:

 a = b + c(d + e).print()

在赋值语句必须用左括号开头的情况下,程序员在前面语句的结束位置明确地提供一个分号是个好主意,而不是依赖于自动分号插入。