詞法語法
本頁介紹 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 MONGOLIAN VOWEL SEPARATOR 從 "Space_Separator" 類別移動到 "Format (Cf)" 類別,並使其成為非空白字元。隨後,"\u180E".trim().length 的結果從 0 變為 1。
行終止符
除了空白字元,行終止符字元也用於提高原始碼的可讀性。然而,在某些情況下,行終止符會影響 JavaScript 程式碼的執行,因為在某些地方它們是被禁止的。行終止符還會影響自動分號插入的過程。
在詞法語法的上下文之外,空白和行終止符通常被混淆。例如,String.prototype.trim() 從字串的開頭和結尾刪除所有空白和行終止符。正則表示式中的 \s 字元類轉義匹配所有空白和行終止符。
只有以下 Unicode 碼點在 ECMAScript 中被視為行終止符,其他換行符被視為空白字元(例如,下一行,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() 呼叫永遠不會被髮出,因為它在註釋內部。任何數量的程式碼行都可以透過這種方式被停用。
Hashbang 註釋
還有一種特殊的第三種註釋語法,即**hashbang 註釋**。hashbang 註釋的行為與單行(//)註釋完全相同,不同之處在於它以 #! 開頭,並且**只在指令碼或模組的絕對開頭有效**。另請注意,在 #! 之前不允許有任何型別的空白。註釋由 #! 之後直到第一行末尾的所有字元組成;只允許有一個這樣的註釋。
JavaScript 中的 Hashbang 註釋類似於Unix 中的 shebang,它提供了你想要用於執行指令碼的特定 JavaScript 直譯器的路徑。在 hashbang 註釋標準化之前,它已經在 Node.js 等非瀏覽器宿主中被事實實現,在那裡它在傳遞給引擎之前從原始碼文字中刪除。示例如下:
#!/usr/bin/env node
console.log("Hello world");
JavaScript 直譯器會將其視為普通註釋——只有當指令碼在 shell 中直接執行時,它才對 shell 具有語義意義。
警告:如果你想讓指令碼在 shell 環境中直接執行,請以 UTF-8 編碼,不帶BOM。儘管 BOM 不會導致在瀏覽器中執行的程式碼出現任何問題——因為它在 UTF-8 解碼期間被刪除,在原始碼文字被分析之前——但如果 hashbang 前面有一個 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 field
}
lbl: console.log(1); // Label
在 JavaScript 中,識別符號通常由字母數字字元、下劃線 (_) 和美元符號 ($) 組成。識別符號不能以數字開頭。然而,JavaScript 識別符號不僅限於 ASCII — 許多 Unicode 碼點也允許使用。具體來說:
- 起始字元可以是ID_Start 類別中的任何字元,加上
_和$。 - 在第一個字元之後,你可以使用ID_Continue 類別中的任何字元,加上 U+200C (ZWNJ) 和 U+200D (ZWJ)。
注意:如果出於某種原因,你需要自己解析某些 JavaScript 原始碼,請不要假設所有識別符號都遵循模式 /[A-Za-z_$][\w$]*/(即,僅 ASCII)!識別符號的範圍可以用正則表示式 /[$_\p{ID_Start}][$\p{ID_Continue}]*/u 來描述(不包括 Unicode 轉義序列)。
此外,JavaScript 允許在識別符號中使用 \u0000 或 \u{000000} 形式的Unicode 轉義序列,它們編碼的字串值與實際的 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";
}
關鍵詞
關鍵字是看起來像識別符號但在 JavaScript 中具有特殊含義的標記。例如,函式宣告前的關鍵字 async 表示該函式是非同步的。
有些關鍵字是**保留**的,這意味著它們不能用作變數宣告、函式宣告等的識別符號。它們通常被稱為**保留字**。保留字的列表如下所示。並非所有關鍵字都是保留的——例如,async 可以在任何地方用作識別符號。有些關鍵字只在**上下文中保留**——例如,await 只在非同步函式體中保留,let 只在嚴格模式程式碼中或 const 和 let 宣告中保留。
識別符號總是按*字串值*比較,因此轉義序列會被解釋。例如,這仍然是一個語法錯誤
const els\u{65} = 1;
// `els\u{65}` encodes the same identifier as `else`
保留字
這些關鍵字不能在 JavaScript 源的任何地方用作變數、函式、類等的識別符號。
breakcasecatchclassconstcontinuedebuggerdefaultdeletedoelseexportextendsfalsefinallyforfunctionifimportininstanceofnewnullreturnsuperswitchthisthrowtruetrytypeofvarvoidwhilewith
以下僅在嚴格模式程式碼中被保留
以下僅在模組程式碼或非同步函式體中被保留
未來保留字
以下是 ECMAScript 規範保留的未來關鍵字。它們目前沒有特殊功能,但將來可能會有,因此不能用作識別符號。
這些始終保留
enum
以下僅在嚴格模式程式碼中被保留
implementsinterfacepackageprivateprotectedpublic
舊標準中的未來保留字
以下是舊版 ECMAScript 規範(ECMAScript 1 到 3)保留的未來關鍵字。
abstractbooleanbytechardoublefinalfloatgotointlongnativeshortsynchronizedthrowstransientvolatile
具有特殊含義的識別符號
少數識別符號在某些上下文中具有特殊含義,但它們並非任何型別的保留字。它們包括
arguments(不是關鍵字,但在嚴格模式下不能宣告為識別符號)as(import * as ns from "mod")asynceval(不是關鍵字,但在嚴格模式下不能宣告為識別符號)from(import x from "mod")getofset
字面量
Null 字面量
另請參閱 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) 的字元都將終止字面量序列。
0xFFFFFFFFFFFFF // 4503599627370495
0xabcdef123456 // 188900967593046
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";
正則表示式字面量
正則表示式字面量由兩個正斜槓 (/) 括起來。詞法分析器會消耗所有字元,直到下一個未轉義的正斜槓或行尾,除非正斜槓出現在字元類 ([]) 內。某些字元(即那些是識別符號部分的字元)可以出現在閉合斜槓之後,表示標誌。
詞法語法非常寬鬆:並非所有被識別為一個標記的正則表示式字面量都是有效的正則表示式。
另請參閱 RegExp 以獲取更多資訊。
/ab+c/g;
/[/]/;
正則表示式字面量不能以兩個正斜槓 (//) 開頭,因為那將是行註釋。要指定空正則表示式,請使用 /(?:)/。
模板字面量
一個模板字面量由幾個標記組成:`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 語句的語法定義要求在末尾使用分號 (;)。它們包括
var,let,const,using,await using- 表示式語句
do...whilecontinue,break,return,throwdebugger- 類欄位宣告(公共或私有)
import,export
然而,為了使語言更易於使用和方便,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> lblbreak <here> lblreturn <here> exprthrow <here> expryield <here> expryield <here> * expr(param) <here> => {}async <here> function,async <here> prop(),async <here> function*,async <here> *prop(),async <here> (param) <here> => {}using <here> id,await <here> using <here> id
這裡 ++ 不被視為應用於變數 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 is not a function”和“Cannot read properties of undefined (reading '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 operationjsconst 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 + bjsconst foo = (a, b) => a + b -
非同步函式、方法等的
async不能直接跟在行終止符之後。jsasync function foo() {}jsasync function foo() {} -
using和await using語句中的using關鍵字應與其宣告的第一個識別符號在同一行。jsusing resource = acquireResource()jsusing resource = acquireResource() -
如果一行以
(、[、`、+、-、/(如正則表示式字面量)之一開頭,則在其前面加上分號,或在前一行末尾加上分號。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® 2026 語言規範 |
瀏覽器相容性
載入中…
另見
- 語法和型別指南
- ES6 的微功能,現已在 Firefox Aurora 和 Nightly 中:二進位制和八進位制數字 by Jeff Walden (2013)
- JavaScript 字元轉義序列 by Mathias Bynens (2011)