词法语法
此页面描述了 JavaScript 的词法语法。JavaScript 源代码只是一系列字符——为了让解释器理解它,字符串必须被解析成更结构化的表示形式。解析的初始步骤称为词法分析,其中文本从左到右扫描并转换为一系列独立的、原子的输入元素。一些输入元素对解释器来说无关紧要,并且将在此步骤之后被删除——它们包括空白和注释。其他的,包括标识符、关键字、字面量和标点符号(主要是运算符),将用于进一步的语法分析。换行符和多行注释在语法上也无关紧要,但它们指导自动分号插入的过程,使某些无效的标记序列变为有效。
格式控制字符
空白符
空白字符提高了源代码的可读性,并将标记彼此分开。这些字符通常对于代码的功能来说是不必要的。缩小工具通常用于删除空白以减少需要传输的数据量。
代码点 | 名称 | 缩写 | 描述 | 转义序列 |
---|---|---|---|---|
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 被视为空白)。
注释
注释用于向 JavaScript 代码添加提示、注释、建议或警告。这可以使其更易于阅读和理解。它们还可以用于禁用代码以防止其执行;这可能是一个有价值的调试工具。
JavaScript 有两种长期以来添加代码注释的方法:行注释和块注释。此外,还有一个特殊的井号注释语法。
行注释
第一种方法是 //
注释;这会将跟随它的同一行上的所有文本都变成注释。例如
function comment() {
// This is a one line JavaScript comment
console.log("Hello world!");
}
comment();
块注释
第二种方法是 /* */
样式,它更加灵活。
例如,您可以在一行上使用它
function comment() {
/* This is a one line JavaScript comment */
console.log("Hello world!");
}
comment();
您还可以创建多行注释,如下所示
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();
如果您愿意,您也可以在行的中间使用它,尽管这会使您的代码更难阅读,因此应谨慎使用
function comment(x) {
console.log("Hello " + x /* insert the value of x */ + " !");
}
comment("world");
此外,您可以使用它来禁用代码以防止其运行,方法是将代码包装在注释中,如下所示
function comment() {
/* console.log("Hello world!"); */
}
comment();
在这种情况下,console.log()
调用从未发出,因为它在注释内。任何数量的代码行都可以通过这种方式禁用。
井号注释
有一种特殊的第三种注释语法,即**井号注释**。井号注释的行为与单行(//
)注释完全相同,除了它以 #!
开头,并且**仅在脚本或模块的绝对开头有效**。另请注意,在 #!
之前不允许任何类型的空白。注释由 #!
之后到第一行结束的所有字符组成;只允许一个这样的注释。
JavaScript 中的井号感叹号注释类似于 Unix 中的 Shebang,它提供要用于执行脚本的特定 JavaScript 解释器的路径。在井号感叹号注释标准化之前,它已经在非浏览器宿主(如 Node.js)中被实际应用,在传递给引擎之前,它会被从源代码文本中剥离。一个例子如下所示
#!/usr/bin/env node
console.log("Hello world");
JavaScript 解释器会将其视为普通注释——只有在 shell 中直接运行脚本时,它才对 shell 具有语义含义。
警告:如果希望脚本能够在 shell 环境中直接运行,请使用 UTF-8 编码,并且不要使用 BOM。虽然 BOM 不会导致在浏览器中运行的代码出现任何问题(因为它在 UTF-8 解码期间,在分析源代码文本之前就被剥离了),但如果 BOM 字符出现在井号感叹号之前,Unix/Linux shell 将无法识别它。
只能使用 #!
注释样式来指定 JavaScript 解释器。在所有其他情况下,只需使用 //
注释(或多行注释)。
标识符
标识符用于将值与名称关联。标识符可以在各种地方使用
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
是相同的标识符
const 你好 = "Hello";
console.log(\u4f60\u597d); // Hello
并非所有地方都接受完整的标识符范围。某些语法,例如函数声明、函数表达式和变量声明,要求使用非 保留字 的标识符名称。
function import() {} // Illegal: import is a reserved word.
最值得注意的是,私有属性和对象属性允许使用保留字。
const obj = { import: "value" }; // Legal despite `import` being reserved
class C {
#import = "value";
}
关键字
关键字是看起来像标识符但具有特殊含义的标记。例如,函数声明之前的关键字 async
表示该函数是异步的。
有些关键字是保留的,这意味着它们不能用作变量声明、函数声明等的标识符。它们通常被称为保留字。下面提供了 这些保留字的列表。并非所有关键字都是保留的——例如,async
可以用作任何地方的标识符。一些关键字仅在特定上下文中保留——例如,await
仅在异步函数的主体中保留,let
仅在严格模式代码中保留,或者 const
和 let
声明中保留。
标识符总是按字符串值进行比较,因此会解释转义序列。例如,这仍然是一个语法错误
const els\u{65} = 1;
// `els\u{65}` encodes the same identifier as `else`
保留字
未来保留字
以下由 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
具有特殊含义的标识符
一些标识符在某些上下文中具有特殊含义,但没有任何类型的保留字。它们包括
arguments
(不是关键字,但在严格模式下不能声明为标识符)as
(import * as ns from "mod"
)async
eval
(不是关键字,但在严格模式下不能声明为标识符)from
(import x from "mod"
)get
of
set
字面量
空值字面量
另请参阅 null
以获取更多信息。
null
布尔值字面量
另请参阅 布尔值类型 以获取更多信息。
true
false
数字字面量
十进制
1234567890
42
十进制字面量可以以零 (0
) 后跟另一个十进制数字开头,但如果前导 0
之后的全部数字都小于 8,则该数字将被解释为八进制数。这被认为是遗留语法,并且以 0
为前缀的数字字面量(无论解释为八进制还是十进制),在 严格模式 中会导致语法错误——因此,请改用 0o
前缀。
0888 // 888 parsed as decimal
0777 // parsed as octal, 511 in decimal
指数
十进制指数字面量由以下格式指定:beN
;其中 b
是基数(整数或浮点数),后跟 E
或 e
字符(用作分隔符或指数指示符)和 N
,它是指数或幂数——一个带符号的整数。
0e-5 // 0
0e+5 // 0
5e1 // 50
175e-2 // 1.75
1e3 // 1000
1e-3 // 0.001
1E3 // 1000
二进制
二进制数语法使用前导零后跟小写或大写拉丁字母“B”(0b
或 0B
)。0b
之后的任何不是 0 或 1 的字符都将终止字面量序列。
0b10000000000000000000000000000000 // 2147483648
0b01111111100000000000000000000000 // 2139095040
0B00000000011111111111111111111111 // 8388607
八进制
八进制数语法使用前导零后跟小写或大写拉丁字母“O”(0o
或 0O)
。0o
之后的任何不在范围 (01234567) 内的字符都将终止字面量序列。
0O755 // 493
0o644 // 420
十六进制
十六进制数语法使用前导零后跟小写或大写拉丁字母“X”(0x
或 0X
)。0x
之后的任何不在范围 (0123456789ABCDEF) 内的字符都将终止字面量序列。
0xFFFFFFFFFFFFFFFFF // 295147905179352830000
0x123456789ABCDEF // 81985529216486900
0XA // 10
BigInt 字面量
BigInt 类型是 JavaScript 中的一种数值基元,可以表示具有任意精度的整数。BigInt 字面量通过在整数末尾附加 n
来创建。
123456789123456789n // 123456789123456789
0o777777777777n // 68719476735
0x123456789ABCDEFn // 81985529216486895
0b11101001010101010101n // 955733
BigInt 字面量不能以 0
开头,以避免与旧的八进制字面量混淆。
0755n; // SyntaxError: invalid BigInt syntax
对于八进制 BigInt
数,始终使用零后跟字母“o”(大写或小写)
0o755n;
有关 BigInt
的更多信息,另请参阅 JavaScript 数据结构。
数字分隔符
为了提高数字字面量的可读性,可以使用下划线 (_
, U+005F
) 作为分隔符
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
请注意这些限制
// 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 编码。
'foo'
"bar"
以下小节描述了字符串字面量中可用的各种转义序列(\
后跟一个或多个字符)。任何下面未列出的转义序列都将成为“标识转义”,它本身成为代码点。例如,\z
与 z
相同。已弃用和已过时的功能 页面中描述了已弃用的八进制转义序列语法。许多这些转义序列在正则表达式中也有效——请参阅 字符转义。
转义序列
可以使用转义序列对特殊字符进行编码
转义序列 | 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) |
\ 后跟 换行符 |
空字符串 |
最后一个转义序列,\
后跟换行符,用于在不更改其含义的情况下将字符串字面量拆分为多行。
const longString =
"This is a very long string which needs \
to wrap across multiple lines because \
otherwise my code is unreadable.";
确保反斜杠后没有空格或任何其他字符(换行符除外),否则它将不起作用。如果下一行缩进,则额外的空格也将存在于字符串的值中。
还可以使用 +
运算符将多个字符串追加在一起,如下所示
const longString =
"This is a very long string which needs " +
"to wrap across multiple lines because " +
"otherwise my code is unreadable.";
以上两种方法产生的字符串相同。
十六进制转义序列
十六进制转义序列由 \x
后跟正好两个十六进制数字组成,表示范围为 0x0000 到 0x00FF 的代码单元或代码点。
"\xA9"; // "©"
Unicode 转义序列
Unicode 转义序列由紧跟在\u
之后的恰好四个十六进制数字组成。它表示 UTF-16 编码中的一个代码单元。对于代码点 U+0000 到 U+FFFF,代码单元等于代码点。代码点 U+10000 到 U+10FFFF 需要两个转义序列来表示用于编码字符的两个代码单元(代理对);代理对与代码点不同。
另请参阅 String.fromCharCode()
和 String.prototype.charCodeAt()
。
"\u00A9"; // "©" (U+A9)
Unicode 代码点转义
Unicode 代码点转义由\u{
、随后是十六进制表示的代码点,最后是}
组成。十六进制数字的值必须在 0 和 0x10FFFF(含)之间。U+10000 到 U+10FFFF 范围内的代码点不需要表示为代理对。
另请参阅 String.fromCodePoint()
和 String.prototype.codePointAt()
。
"\u{2F804}"; // CJK COMPATIBILITY IDEOGRAPH-2F804 (U+2F804)
// the same character represented as a surrogate pair
"\uD87E\uDC04";
正则表达式字面量
模板字面量
一个模板字面量由多个标记组成:`xxx${
(模板头)、}xxx${
(模板中间)和}xxx`
(模板尾)是单个标记,而任何表达式都可能出现在它们之间。
另请参阅 模板字面量 以获取更多信息。
`string text`
`string text line 1
string text line 2`
`string text ${expression} string text`
tag`string text ${expression} string text`
自动分号插入
一些 JavaScript 语句 的语法定义需要在末尾使用分号 (;
)。它们包括
但是,为了使语言更易于使用和方便,JavaScript 能够在使用标记流时自动插入分号,以便可以将某些无效的标记序列“修复”为有效的语法。此步骤发生在程序文本根据词法语法解析为标记之后。自动插入分号有三种情况
1. 当遇到语法不允许的标记时,并且它与前一个标记之间至少隔了一个 换行符(包括包含至少一个换行符的块注释),或者标记是“}”,则在该标记之前插入一个分号。
{ 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
的结束“)”也由此规则作为特殊情况处理。
do {
// ...
} while (condition) /* ; */ // ASI here
const a = 1
但是,如果分号随后成为 for
语句头部中的分隔符,则不会插入分号。
for (
let a = 1 // No ASI here
a < 10 // No ASI here
a++
) {}
分号也永远不会作为 空语句 插入。例如,在下面的代码中,如果在“)”之后插入分号,则代码将有效,其中空语句作为if
主体,而const
声明是单独的语句。但是,由于自动插入的分号不能成为空语句,因此这会导致 声明 成为if
语句的主体,这无效。
if (Math.random() > 0.5)
const x = 1 // SyntaxError: Unexpected token 'const'
2. 当到达标记输入流的末尾时,并且解析器无法将单个输入流解析为完整的程序,则在末尾插入一个分号。
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> function
、async <here> prop()
、async <here> function*
、async <here> *prop()
、async <here> (param) <here> => {}
此处 ++
未被视为应用于变量b
的后缀运算符,因为b
和++
之间出现了一个换行符。
a = b
++c
// is transformed by ASI into
a = b;
++c;
此处,return
语句返回undefined
,而a + b
成为无法访问的语句。
return
a + b
// is transformed by ASI into
return;
a + b;
请注意,只有当换行符分隔否则会导致无效语法的标记时,才会触发 ASI。如果下一个标记可以作为有效结构的一部分进行解析,则不会插入分号。例如
const a = 1
(1).toString()
const b = 1
[1, 2, 3].forEach(console.log)
因为()
可以被视为函数调用,所以通常不会触发 ASI。类似地,[]
可能是成员访问。上面的代码等价于
const a = 1(1).toString();
const b = 1[1, 2, 3].forEach(console.log);
这恰好是有效的语法。1[1, 2, 3]
是一个使用 逗号 连接的表达式进行 属性访问。因此,在运行代码时,您会收到类似“1 不是函数”和“无法读取未定义的属性(读取'forEach')”的错误。
在类中,类字段和生成器方法也可能是一个陷阱。
class A {
a = 1
*gen() {}
}
它被视为
class A {
a = 1 * gen() {}
}
因此,在{
周围将发生语法错误。
如果您想强制执行无分号样式,则有以下经验法则可用于处理 ASI
- 将后缀
++
和--
与它们的运算对象写在同一行。jsconst a = b ++ console.log(a) // ReferenceError: Invalid left-hand side expression in prefix operation
jsconst a = b++ console.log(a)
return
、throw
或yield
之后的表达式应与关键字位于同一行。jsfunction foo() { return 1 + 1 // Returns undefined; 1 + 1 is ignored }
jsfunction foo() { return 1 + 1 } function foo() { return ( 1 + 1 ) }
- 类似地,
break
或continue
之后的标签标识符应与关键字位于同一行。jsouterBlock: { innerBlock: { break outerBlock // SyntaxError: Illegal break statement } }
jsouterBlock: { innerBlock: { break outerBlock } }
- 箭头函数的
=>
应与其参数的结尾位于同一行。jsconst foo = (a, b) => a + b
jsconst foo = (a, b) => a + b
- 异步函数、方法等的
async
后面不能直接跟换行符。jsasync function foo() {}
jsasync 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)
- 类字段最好始终以分号结尾——除了上一条规则(包括后跟 计算属性 的字段声明,因为后者以
[
开头)之外,在字段声明和生成器方法之间也需要分号。jsclass A { a = 1 [b] = 2 *gen() {} // Seen as a = 1[b] = 2 * gen() {} }
jsclass A { a = 1; [b] = 2; *gen() {} }
规范
规范 |
---|
ECMAScript 语言规范 |
浏览器兼容性
BCD 表仅在启用 JavaScript 的浏览器中加载。
另请参阅
- 语法和类型 指南
- ES6 的微特性,现已在 Firefox Aurora 和 Nightly 中提供:二进制和八进制数字,作者:Jeff Walden(2013)
- JavaScript 字符转义序列,作者:Mathias Bynens(2011)