词法语法

此页面描述了 JavaScript 的词法语法。JavaScript 源代码只是一系列字符——为了让解释器理解它,字符串必须被解析成更结构化的表示形式。解析的初始步骤称为词法分析,其中文本从左到右扫描并转换为一系列独立的、原子的输入元素。一些输入元素对解释器来说无关紧要,并且将在此步骤之后被删除——它们包括空白注释。其他的,包括标识符关键字字面量和标点符号(主要是运算符),将用于进一步的语法分析。换行符和多行注释在语法上也无关紧要,但它们指导自动分号插入的过程,使某些无效的标记序列变为有效。

格式控制字符

格式控制字符没有视觉表示,但用于控制文本的解释。

代码点 名称 缩写 描述
U+200C 零宽非连接符 <ZWNJ> 放置在字符之间以防止在某些语言中连接成连字 (维基百科)。
U+200D 零宽连接符 <ZWJ> 放置在通常不会连接的字符之间,以便在某些语言中使用它们的连接形式呈现字符 (维基百科)。
U+FEFF 字节顺序标记 <BOM> 用于脚本开头,将其标记为 Unicode 和文本的字节顺序 (维基百科)。

在 JavaScript 源代码中,<ZWNJ> 和 <ZWJ> 被视为标识符的一部分,而 <BOM>(当不在文本开头时也称为零宽不换行空格 <ZWNBSP>)被视为空白

空白符

空白字符提高了源代码的可读性,并将标记彼此分开。这些字符通常对于代码的功能来说是不必要的。缩小工具通常用于删除空白以减少需要传输的数据量。

代码点 名称 缩写 描述 转义序列
U+0009 字符制表符 <TAB> 水平制表符 \t
U+000B 换行制表符 <VT> 垂直制表符 \v
U+000C 换页符 <FF> 页面中断控制字符 (维基百科)。 \f
U+0020 空格 <SP> 普通空格
U+00A0 不换行空格 <NBSP> 普通空格,但没有允许换行的点
U+FEFF 零宽不换行空格 <ZWNBSP> 当不在脚本开头时,BOM 标记是一个普通的空白字符。
其他 其他 Unicode 空格字符 <USP> "Space_Separator" 通用类别中的字符

注意:在那些具有 "White_Space" 属性但不在 "Space_Separator" 通用类别中的字符中,U+0009、U+000B 和 U+000C 在 JavaScript 中仍被视为空白;U+0085 NEXT LINE 没有特殊作用;其他字符成为换行符的集合。

注意:JavaScript 引擎使用的 Unicode 标准的更改可能会影响程序的行为。例如,ES2016 将参考 Unicode 标准从 5.1 升级到 8.0.0,这导致 U+180E 蒙古语元音分隔符从 "Space_Separator" 类别移动到 "Format (Cf)" 类别,并使其成为非空白。随后,"\u180E".trim().length 的结果从 0 变为 1

行终止符

除了空白字符外,换行符字符还用于提高源代码的可读性。但是,在某些情况下,换行符会影响 JavaScript 代码的执行,因为在一些地方禁止使用它们。换行符还会影响自动分号插入的过程。

在词法语法的上下文中,空白和换行符通常会被混淆。例如,String.prototype.trim() 删除字符串开头和结尾的所有空白和换行符。正则表达式中的 \s 字符类转义 匹配所有空白和换行符。

在 ECMAScript 中,只有以下 Unicode 代码点被视为换行符,其他换行字符被视为空白(例如,下一行、NEL、U+0085 被视为空白)。

代码点 名称 缩写 描述 转义序列
U+000A 换行符 <LF> UNIX 系统中的换行符。 \n
U+000D 回车符 <CR> Commodore 和早期 Mac 系统中的换行符。 \r
U+2028 换行分隔符 <LS> 维基百科
U+2029 段落分隔符 <PS> 维基百科

注释

注释用于向 JavaScript 代码添加提示、注释、建议或警告。这可以使其更易于阅读和理解。它们还可以用于禁用代码以防止其执行;这可能是一个有价值的调试工具。

JavaScript 有两种长期以来添加代码注释的方法:行注释和块注释。此外,还有一个特殊的井号注释语法。

行注释

第一种方法是 // 注释;这会将跟随它的同一行上的所有文本都变成注释。例如

js
function comment() {
  // This is a one line JavaScript comment
  console.log("Hello world!");
}
comment();

块注释

第二种方法是 /* */ 样式,它更加灵活。

例如,您可以在一行上使用它

js
function comment() {
  /* This is a one line JavaScript comment */
  console.log("Hello world!");
}
comment();

您还可以创建多行注释,如下所示

js
function comment() {
  /* This comment spans multiple lines. Notice
     that we don't need to end the comment until we're done. */
  console.log("Hello world!");
}
comment();

如果您愿意,您也可以在行的中间使用它,尽管这会使您的代码更难阅读,因此应谨慎使用

js
function comment(x) {
  console.log("Hello " + x /* insert the value of x */ + " !");
}
comment("world");

此外,您可以使用它来禁用代码以防止其运行,方法是将代码包装在注释中,如下所示

js
function comment() {
  /* console.log("Hello world!"); */
}
comment();

在这种情况下,console.log() 调用从未发出,因为它在注释内。任何数量的代码行都可以通过这种方式禁用。

包含至少一个换行符的块注释在自动分号插入中表现得像换行符

井号注释

有一种特殊的第三种注释语法,即**井号注释**。井号注释的行为与单行(//)注释完全相同,除了它以 #! 开头,并且**仅在脚本或模块的绝对开头有效**。另请注意,在 #! 之前不允许任何类型的空白。注释由 #! 之后到第一行结束的所有字符组成;只允许一个这样的注释。

JavaScript 中的井号感叹号注释类似于 Unix 中的 Shebang,它提供要用于执行脚本的特定 JavaScript 解释器的路径。在井号感叹号注释标准化之前,它已经在非浏览器宿主(如 Node.js)中被实际应用,在传递给引擎之前,它会被从源代码文本中剥离。一个例子如下所示

js
#!/usr/bin/env node

console.log("Hello world");

JavaScript 解释器会将其视为普通注释——只有在 shell 中直接运行脚本时,它才对 shell 具有语义含义。

警告:如果希望脚本能够在 shell 环境中直接运行,请使用 UTF-8 编码,并且不要使用 BOM。虽然 BOM 不会导致在浏览器中运行的代码出现任何问题(因为它在 UTF-8 解码期间,在分析源代码文本之前就被剥离了),但如果 BOM 字符出现在井号感叹号之前,Unix/Linux shell 将无法识别它。

只能使用 #! 注释样式来指定 JavaScript 解释器。在所有其他情况下,只需使用 // 注释(或多行注释)。

标识符

标识符用于将值与名称关联。标识符可以在各种地方使用

js
const decl = 1; // Variable declaration (may also be `let` or `var`)
function fn() {} // Function declaration
const obj = { key: "value" }; // Object keys
// Class declaration
class C {
  #priv = "value"; // Private property
}
lbl: console.log(1); // Label

在 JavaScript 中,标识符通常由字母数字字符、下划线 (_) 和美元符号 ($) 组成。标识符不能以数字开头。但是,JavaScript 标识符不仅限于 ASCII——许多 Unicode 代码点也允许使用。具体来说,ID_Start 类别中的任何字符都可以作为标识符的开头,而 ID_Continue 类别中的任何字符都可以在第一个字符之后出现。

注意:如果出于某种原因需要自己解析一些 JavaScript 源代码,请不要假设所有标识符都遵循 /[A-Za-z_$][\w$]*/ 模式(即仅限 ASCII)!标识符的范围可以用正则表达式 /[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*/u 来描述(不包括 Unicode 转义序列)。

此外,JavaScript 允许在标识符中使用 Unicode 转义序列,形式为 \u0000\u{000000},它们与实际的 Unicode 字符编码相同的字符串值。例如,你好\u4f60\u597d 是相同的标识符

js
const 你好 = "Hello";
console.log(\u4f60\u597d); // Hello

并非所有地方都接受完整的标识符范围。某些语法,例如函数声明、函数表达式和变量声明,要求使用非 保留字 的标识符名称。

js
function import() {} // Illegal: import is a reserved word.

最值得注意的是,私有属性和对象属性允许使用保留字。

js
const obj = { import: "value" }; // Legal despite `import` being reserved
class C {
  #import = "value";
}

关键字

关键字是看起来像标识符但具有特殊含义的标记。例如,函数声明之前的关键字 async 表示该函数是异步的。

有些关键字是保留的,这意味着它们不能用作变量声明、函数声明等的标识符。它们通常被称为保留字。下面提供了 这些保留字的列表。并非所有关键字都是保留的——例如,async 可以用作任何地方的标识符。一些关键字仅在特定上下文中保留——例如,await 仅在异步函数的主体中保留,let 仅在严格模式代码中保留,或者 constlet 声明中保留。

标识符总是按字符串值进行比较,因此会解释转义序列。例如,这仍然是一个语法错误

js
const els\u{65} = 1;
// `els\u{65}` encodes the same identifier as `else`

保留字

这些关键字不能用作变量、函数、类等的标识符,JavaScript 源代码中的任何地方都不允许。

以下仅在严格模式代码中找到时才保留

  • let(在 constlet 和类声明中也保留)
  • static
  • yield(在生成器函数体中也保留)

以下仅在模块代码或异步函数体中找到时才保留

未来保留字

以下由 ECMAScript 规范保留为将来的关键字。它们目前没有特殊的功能,但将来可能会有一些功能,因此不能用作标识符。

这些始终保留

  • enum

以下仅在严格模式代码中找到时才保留

  • implements
  • interface
  • package
  • private
  • protected
  • public

旧标准中的未来保留字

以下由旧的 ECMAScript 规范(ECMAScript 1 到 3)保留为将来的关键字。

  • abstract
  • boolean
  • byte
  • char
  • double
  • final
  • float
  • goto
  • int
  • long
  • native
  • short
  • synchronized
  • throws
  • transient
  • volatile

具有特殊含义的标识符

一些标识符在某些上下文中具有特殊含义,但没有任何类型的保留字。它们包括

字面量

注意:本节讨论作为原子标记的字面量。 对象字面量数组字面量 是由一系列标记组成的 表达式

空值字面量

另请参阅 null 以获取更多信息。

js
null

布尔值字面量

另请参阅 布尔值类型 以获取更多信息。

js
true
false

数字字面量

NumberBigInt 类型使用数字字面量。

十进制

js
1234567890
42

十进制字面量可以以零 (0) 后跟另一个十进制数字开头,但如果前导 0 之后的全部数字都小于 8,则该数字将被解释为八进制数。这被认为是遗留语法,并且以 0 为前缀的数字字面量(无论解释为八进制还是十进制),在 严格模式 中会导致语法错误——因此,请改用 0o 前缀。

js
0888 // 888 parsed as decimal
0777 // parsed as octal, 511 in decimal
指数

十进制指数字面量由以下格式指定:beN;其中 b 是基数(整数或浮点数),后跟 Ee 字符(用作分隔符或指数指示符)和 N,它是指数数——一个带符号的整数。

js
0e-5   // 0
0e+5   // 0
5e1    // 50
175e-2 // 1.75
1e3    // 1000
1e-3   // 0.001
1E3    // 1000

二进制

二进制数语法使用前导零后跟小写或大写拉丁字母“B”(0b0B)。0b 之后的任何不是 0 或 1 的字符都将终止字面量序列。

js
0b10000000000000000000000000000000 // 2147483648
0b01111111100000000000000000000000 // 2139095040
0B00000000011111111111111111111111 // 8388607

八进制

八进制数语法使用前导零后跟小写或大写拉丁字母“O”(0o0O)0o 之后的任何不在范围 (01234567) 内的字符都将终止字面量序列。

js
0O755 // 493
0o644 // 420

十六进制

十六进制数语法使用前导零后跟小写或大写拉丁字母“X”(0x0X)。0x 之后的任何不在范围 (0123456789ABCDEF) 内的字符都将终止字面量序列。

js
0xFFFFFFFFFFFFFFFFF // 295147905179352830000
0x123456789ABCDEF   // 81985529216486900
0XA                 // 10

BigInt 字面量

BigInt 类型是 JavaScript 中的一种数值基元,可以表示具有任意精度的整数。BigInt 字面量通过在整数末尾附加 n 来创建。

js
123456789123456789n     // 123456789123456789
0o777777777777n         // 68719476735
0x123456789ABCDEFn      // 81985529216486895
0b11101001010101010101n // 955733

BigInt 字面量不能以 0 开头,以避免与旧的八进制字面量混淆。

js
0755n; // SyntaxError: invalid BigInt syntax

对于八进制 BigInt 数,始终使用零后跟字母“o”(大写或小写)

js
0o755n;

有关 BigInt 的更多信息,另请参阅 JavaScript 数据结构

数字分隔符

为了提高数字字面量的可读性,可以使用下划线 (_, U+005F) 作为分隔符

js
1_000_000_000_000
1_050.95
0b1010_0001_1000_0101
0o2_2_5_6
0xA0_B0_C0
1_000_000_000_000_000_000_000n

请注意这些限制

js
// More than one underscore in a row is not allowed
100__000; // SyntaxError

// Not allowed at the end of numeric literals
100_; // SyntaxError

// Can not be used after leading 0
0_1; // SyntaxError

字符串字面量

字符串 字面量是零个或多个 Unicode 代码点,用单引号或双引号括起来。Unicode 代码点也可以用转义序列表示。所有代码点都可以在字符串字面量中直接出现,除了以下代码点

  • U+005C \(反斜杠)
  • U+000D <CR>
  • U+000A <LF>
  • 与开始字符串字面量的引号相同

任何代码点都可以以转义序列的形式出现。字符串字面量计算结果为 ECMAScript 字符串值。在生成这些字符串值时,Unicode 代码点将使用 UTF-16 编码。

js
'foo'
"bar"

以下小节描述了字符串字面量中可用的各种转义序列(\ 后跟一个或多个字符)。任何下面未列出的转义序列都将成为“标识转义”,它本身成为代码点。例如,\zz 相同。已弃用和已过时的功能 页面中描述了已弃用的八进制转义序列语法。许多这些转义序列在正则表达式中也有效——请参阅 字符转义

转义序列

可以使用转义序列对特殊字符进行编码

转义序列 Unicode 代码点
\0 空字符 (U+0000 NULL)
\' 单引号 (U+0027 APOSTROPHE)
\" 双引号 (U+0022 QUOTATION MARK)
\\ 反斜杠 (U+005C REVERSE SOLIDUS)
\n 换行符 (U+000A LINE FEED; LF)
\r 回车符 (U+000D CARRIAGE RETURN; CR)
\v 垂直制表符 (U+000B LINE TABULATION)
\t 制表符 (U+0009 CHARACTER TABULATION)
\b 退格键 (U+0008 BACKSPACE)
\f 换页符 (U+000C FORM FEED)
\ 后跟 换行符 空字符串

最后一个转义序列,\ 后跟换行符,用于在不更改其含义的情况下将字符串字面量拆分为多行。

js
const longString =
  "This is a very long string which needs \
to wrap across multiple lines because \
otherwise my code is unreadable.";

确保反斜杠后没有空格或任何其他字符(换行符除外),否则它将不起作用。如果下一行缩进,则额外的空格也将存在于字符串的值中。

还可以使用 + 运算符将多个字符串追加在一起,如下所示

js
const longString =
  "This is a very long string which needs " +
  "to wrap across multiple lines because " +
  "otherwise my code is unreadable.";

以上两种方法产生的字符串相同。

十六进制转义序列

十六进制转义序列由 \x 后跟正好两个十六进制数字组成,表示范围为 0x0000 到 0x00FF 的代码单元或代码点。

js
"\xA9"; // "©"

Unicode 转义序列

Unicode 转义序列由紧跟在\u之后的恰好四个十六进制数字组成。它表示 UTF-16 编码中的一个代码单元。对于代码点 U+0000 到 U+FFFF,代码单元等于代码点。代码点 U+10000 到 U+10FFFF 需要两个转义序列来表示用于编码字符的两个代码单元(代理对);代理对与代码点不同。

另请参阅 String.fromCharCode()String.prototype.charCodeAt()

js
"\u00A9"; // "©" (U+A9)

Unicode 代码点转义

Unicode 代码点转义由\u{、随后是十六进制表示的代码点,最后是}组成。十六进制数字的值必须在 0 和 0x10FFFF(含)之间。U+10000 到 U+10FFFF 范围内的代码点不需要表示为代理对。

另请参阅 String.fromCodePoint()String.prototype.codePointAt()

js
"\u{2F804}"; // CJK COMPATIBILITY IDEOGRAPH-2F804 (U+2F804)

// the same character represented as a surrogate pair
"\uD87E\uDC04";

正则表达式字面量

正则表达式字面量用两个正斜杠 (/) 括起来。词法分析器会消耗所有字符,直到下一个未转义的正斜杠或行尾,除非正斜杠出现在字符类 ([]) 中。某些字符(即那些是 标识符部分)可以出现在结束斜杠之后,表示标志。

词法语法非常宽松:并非所有被识别为一个标记的正则表达式字面量都是有效的正则表达式。

另请参阅 RegExp 以获取更多信息。

js
/ab+c/g
/[/]/

正则表达式字面量不能以两个正斜杠 (//) 开头,因为那将是行注释。要指定空正则表达式,请使用/(?:)/

模板字面量

一个模板字面量由多个标记组成:`xxx${(模板头)、}xxx${(模板中间)和}xxx`(模板尾)是单个标记,而任何表达式都可能出现在它们之间。

另请参阅 模板字面量 以获取更多信息。

js
`string text`

`string text line 1
 string text line 2`

`string text ${expression} string text`

tag`string text ${expression} string text`

自动分号插入

一些 JavaScript 语句 的语法定义需要在末尾使用分号 (;)。它们包括

但是,为了使语言更易于使用和方便,JavaScript 能够在使用标记流时自动插入分号,以便可以将某些无效的标记序列“修复”为有效的语法。此步骤发生在程序文本根据词法语法解析为标记之后。自动插入分号有三种情况

1. 当遇到语法不允许的标记时,并且它与前一个标记之间至少隔了一个 换行符(包括包含至少一个换行符的块注释),或者标记是“}”,则在该标记之前插入一个分号。

js
{ 1
2 } 3

// is transformed by ASI into:

{ 1
;2 ;} 3;

// Which is valid grammar encoding three statements,
// each consisting of a number literal

do...while 的结束“)”也由此规则作为特殊情况处理。

js
do {
  // ...
} while (condition) /* ; */ // ASI here
const a = 1

但是,如果分号随后成为 for 语句头部中的分隔符,则不会插入分号。

js
for (
  let a = 1 // No ASI here
  a < 10 // No ASI here
  a++
) {}

分号也永远不会作为 空语句 插入。例如,在下面的代码中,如果在“)”之后插入分号,则代码将有效,其中空语句作为if主体,而const声明是单独的语句。但是,由于自动插入的分号不能成为空语句,因此这会导致 声明 成为if语句的主体,这无效。

js
if (Math.random() > 0.5)
const x = 1 // SyntaxError: Unexpected token 'const'

2. 当到达标记输入流的末尾时,并且解析器无法将单个输入流解析为完整的程序,则在末尾插入一个分号。

js
const a = 1 /* ; */ // ASI here

此规则是对前一个规则的补充,专门针对没有“违规标记”但有输入流末尾的情况。

3. 当语法在某些位置禁止换行符但发现换行符时,会插入分号。这些位置包括

  • expr <here> ++expr <here> --
  • continue <here> lbl
  • break <here> lbl
  • return <here> expr
  • throw <here> expr
  • yield <here> expr
  • yield <here> * expr
  • (param) <here> => {}
  • async <here> functionasync <here> prop()async <here> function*async <here> *prop()async <here> (param) <here> => {}

此处 ++ 未被视为应用于变量b的后缀运算符,因为b++之间出现了一个换行符。

js
a = b
++c

// is transformed by ASI into

a = b;
++c;

此处,return语句返回undefined,而a + b成为无法访问的语句。

js
return
a + b

// is transformed by ASI into

return;
a + b;

请注意,只有当换行符分隔否则会导致无效语法的标记时,才会触发 ASI。如果下一个标记可以作为有效结构的一部分进行解析,则不会插入分号。例如

js
const a = 1
(1).toString()

const b = 1
[1, 2, 3].forEach(console.log)

因为()可以被视为函数调用,所以通常不会触发 ASI。类似地,[]可能是成员访问。上面的代码等价于

js
const a = 1(1).toString();

const b = 1[1, 2, 3].forEach(console.log);

这恰好是有效的语法。1[1, 2, 3]是一个使用 逗号 连接的表达式进行 属性访问。因此,在运行代码时,您会收到类似“1 不是函数”和“无法读取未定义的属性(读取'forEach')”的错误。

在类中,类字段和生成器方法也可能是一个陷阱。

js
class A {
  a = 1
  *gen() {}
}

它被视为

js
class A {
  a = 1 * gen() {}
}

因此,在{周围将发生语法错误。

如果您想强制执行无分号样式,则有以下经验法则可用于处理 ASI

  • 将后缀++--与它们的运算对象写在同一行。
    js
    const a = b
    ++
    console.log(a) // ReferenceError: Invalid left-hand side expression in prefix operation
    
    js
    const a = b++
    console.log(a)
    
  • returnthrowyield之后的表达式应与关键字位于同一行。
    js
    function foo() {
      return
        1 + 1 // Returns undefined; 1 + 1 is ignored
    }
    
    js
    function foo() {
      return 1 + 1
    }
    
    function foo() {
      return (
        1 + 1
      )
    }
    
  • 类似地,breakcontinue之后的标签标识符应与关键字位于同一行。
    js
    outerBlock: {
      innerBlock: {
        break
          outerBlock // SyntaxError: Illegal break statement
      }
    }
    
    js
    outerBlock: {
      innerBlock: {
        break outerBlock
      }
    }
    
  • 箭头函数的=>应与其参数的结尾位于同一行。
    js
    const foo = (a, b)
      => a + b
    
    js
    const foo = (a, b) =>
      a + b
    
  • 异步函数、方法等的async后面不能直接跟换行符。
    js
    async
    function foo() {}
    
    js
    async function
    foo() {}
    
  • 如果一行以以下之一开头:([`+-/(如在正则表达式字面量中),请在前面加上分号,或在上一行末尾加上分号。
    js
    // The () may be merged with the previous line as a function call
    (() => {
      // ...
    })()
    
    // The [ may be merged with the previous line as a property access
    [1, 2, 3].forEach(console.log)
    
    // The ` may be merged with the previous line as a tagged template literal
    `string text ${data}`.match(pattern).forEach(console.log)
    
    // The + may be merged with the previous line as a binary + expression
    +a.toString()
    
    // The - may be merged with the previous line as a binary - expression
    -a.toString()
    
    // The / may be merged with the previous line as a division expression
    /pattern/.exec(str).forEach(console.log)
    
    js
    ;(() => {
      // ...
    })()
    ;[1, 2, 3].forEach(console.log)
    ;`string text ${data}`.match(pattern).forEach(console.log)
    ;+a.toString()
    ;-a.toString()
    ;/pattern/.exec(str).forEach(console.log)
    
  • 类字段最好始终以分号结尾——除了上一条规则(包括后跟 计算属性 的字段声明,因为后者以[开头)之外,在字段声明和生成器方法之间也需要分号。
    js
    class A {
      a = 1
      [b] = 2
      *gen() {} // Seen as a = 1[b] = 2 * gen() {}
    }
    
    js
    class A {
      a = 1;
      [b] = 2;
      *gen() {}
    }
    

规范

规范
ECMAScript 语言规范

浏览器兼容性

BCD 表仅在启用 JavaScript 的浏览器中加载。

另请参阅