基礎知識
JavaScript 的大部分語法借鑑了 Java、C 和 C++,但也受到了 Awk、Perl 和 Python 的影響。
JavaScript 區分大小寫,並使用 Unicode 字元集。例如,單詞 Früh(德語中意為“早”)可以用作變數名。
const Früh = "foobar";
但是,變數 früh 與 Früh 不同,因為 JavaScript 區分大小寫。
在 JavaScript 中,指令稱為語句,並用分號 (;) 分隔。
如果語句單獨成行,則不需要分號。但是,如果一行中有多個語句,則它們必須用分號分隔。
然而,最佳實踐是始終在語句後寫入分號,即使在不嚴格需要的情況下也是如此。這種做法可以減少程式碼中出現錯誤的機會。
JavaScript 指令碼的原始碼從左到右進行掃描,並轉換為一系列輸入元素,這些元素是標記、控制字元、行終止符、註釋或空白字元。(空格、製表符和換行符都被視為空白字元。)
註釋
註釋的語法與 C++ 和許多其他語言相同
// a one line comment
/* this is a longer,
* multi-line comment
*/
您不能巢狀塊註釋。當您不小心在註釋中包含了 */ 序列時,通常會發生這種情況,這將終止註釋。
/* You can't, however, /* nest comments */ SyntaxError */
在這種情況下,您需要將 */ 模式拆開。例如,透過插入反斜槓
/* You can /* nest comments *\/ by escaping slashes */
註釋行為類似於空白字元,並在指令碼執行期間被忽略。
注意:您可能還會在某些 JavaScript 檔案的開頭看到第三種註釋語法,它看起來像這樣:#!/usr/bin/env node。
這被稱為 hashbang 註釋語法,是一種特殊註釋,用於指定應執行指令碼的特定 JavaScript 引擎的路徑。有關更多詳細資訊,請參閱Hashbang 註釋。
宣告
JavaScript 有三種變數宣告。
變數
您在應用程式中使用變數作為值的符號名稱。變數的名稱,稱為識別符號,遵循某些規則。
JavaScript 識別符號通常以字母、下劃線 (_) 或美元符號 ($) 開頭。後續字元也可以是數字 (0 – 9)。由於 JavaScript 區分大小寫,字母包括字元 A 到 Z(大寫)以及 a 到 z(小寫)。
您可以在識別符號中使用大多數 Unicode 字母,例如 å 和 ü。(有關更多詳細資訊,請參閱詞法語法參考。)您還可以使用Unicode 轉義序列來表示識別符號中的字元。
一些合法的名稱示例是 Number_hits、temp99、$credit 和 _name。
宣告變數
您可以透過兩種方式宣告變數
- 使用關鍵字
var。例如,var x = 42。此語法可用於宣告區域性變數和全域性變數,具體取決於執行上下文。 - 使用關鍵字
const或let。例如,let y = 13。此語法可用於宣告塊作用域的區域性變數。(請參閱下面的變數作用域。)
您可以使用解構語法宣告變數來解包值。例如,const { bar } = foo。這將建立一個名為 bar 的變數,併為其賦值為我們物件 foo 中同名鍵對應的值。
變數在使用前應始終宣告。JavaScript 過去允許賦值給未宣告的變數,這會建立一個未宣告的全域性變數。這在嚴格模式下是一個錯誤,應完全避免。
宣告與初始化
在像 let x = 42 這樣的語句中,let x 部分稱為宣告,= 42 部分稱為初始化器。宣告允許變數在程式碼中稍後訪問而不會丟擲 ReferenceError,而初始化器為變數賦值。在 var 和 let 宣告中,初始化器是可選的。如果變數未帶初始化器宣告,則它被賦值為 undefined。
let x;
console.log(x); // logs "undefined"
本質上,let x = 42 等同於 let x; x = 42。
const 宣告總是需要一個初始化器,因為它們禁止在聲明後進行任何型別的賦值,並且隱式地用 undefined 初始化很可能是程式設計師的錯誤。
const x; // SyntaxError: Missing initializer in const declaration
變數作用域
變數可能屬於以下作用域之一
- 全域性作用域:指令碼模式下所有程式碼的預設作用域。
- 模組作用域:模組模式下程式碼的作用域。
- 函式作用域:透過函式建立的作用域。
此外,用 let 或 const 宣告的變數可以屬於一個額外的作用域
- 塊作用域:由一對花括號(一個塊)建立的作用域。
當您在任何函式之外宣告變數時,它被稱為全域性變數,因為它可用於當前文件中的任何其他程式碼。當您在函式內部宣告變數時,它被稱為區域性變數,因為它僅在該函式內部可用。
let 和 const 宣告也可以作用域到它們宣告的塊語句。
if (Math.random() > 0.5) {
const y = 5;
}
console.log(y); // ReferenceError: y is not defined
然而,用 var 建立的變數不是塊作用域的,而只是塊所在函式(或全域性作用域)的區域性變數。
例如,以下程式碼將輸出 5,因為 x 的作用域是全域性上下文(如果程式碼是函式的一部分,則為函式上下文)。x 的作用域不限於緊鄰的 if 語句塊。
if (true) {
var x = 5;
}
console.log(x); // x is 5
變數提升
用 var 宣告的變數會被提升,這意味著您可以在其作用域中的任何位置引用該變數,即使其宣告尚未到達。您可以將 var 宣告視為被“提升”到其函式或全域性作用域的頂部。但是,如果您在變數宣告之前訪問它,其值始終為 undefined,因為只有其宣告和預設初始化(使用 undefined)會被提升,而不是其值賦值。
console.log(x === undefined); // true
var x = 3;
(function () {
console.log(x); // undefined
var x = "local value";
})();
上面的例子將被解釋為與
var x;
console.log(x === undefined); // true
x = 3;
(function () {
var x;
console.log(x); // undefined
x = "local value";
})();
由於提升,函式中的所有 var 語句都應儘可能地放置在函式的頂部。這種最佳實踐提高了程式碼的清晰度。
let 和 const 是否提升是定義上的爭論。在變數宣告之前在塊中引用變數總是會導致 ReferenceError,因為該變數從塊開始直到宣告被處理為止都處於“暫時死區”中。
console.log(x); // ReferenceError
const x = 3;
console.log(y); // ReferenceError
let y = 3;
與 var 宣告不同,var 宣告只提升宣告而不提升其值,而函式宣告是完全提升的——您可以安全地在其作用域中的任何位置呼叫該函式。有關更多討論,請參閱提升詞彙表條目。
全域性變數
全域性變數實際上是全域性物件的屬性。
在網頁中,全域性物件是 window,因此您可以使用 window.variable 語法讀取和設定全域性變數。在所有環境中,globalThis 變數(它本身也是一個全域性變數)可用於讀取和設定全域性變數。這是為了在各種 JavaScript 執行時之間提供一致的介面。
因此,您可以透過指定 window 或 frame 名稱從一個視窗或框架訪問在另一個視窗或框架中宣告的全域性變數。例如,如果在文件中聲明瞭一個名為 phoneNumber 的變數,您可以從 iframe 中將其引用為 parent.phoneNumber。
常量
您可以使用 const 關鍵字建立一個只讀的命名常量。常量識別符號的語法與任何變數識別符號相同:它必須以字母、下劃線或美元符號 ($) 開頭,並且可以包含字母、數字或下劃線字元。
const PI = 3.14;
在指令碼執行時,常量不能透過賦值改變值,也不能重新宣告。它必須初始化為一個值。常量的作用域規則與 let 塊作用域變數相同。
您不能在同一作用域中宣告與函式或變數同名的常量。例如
// THIS WILL CAUSE AN ERROR
function f() {}
const f = 5;
// THIS WILL CAUSE AN ERROR TOO
function f() {
const g = 5;
var g;
}
但是,const 只阻止重新賦值,而不阻止突變。賦值給常量的物件的屬性不受保護,因此以下語句可以正常執行。
const MY_OBJECT = { key: "value" };
MY_OBJECT.key = "otherValue";
同樣,陣列的內容也不受保護,因此以下語句可以正常執行。
const MY_ARRAY = ["HTML", "CSS"];
MY_ARRAY.push("JAVASCRIPT");
console.log(MY_ARRAY); // ['HTML', 'CSS', 'JAVASCRIPT'];
資料結構和型別
資料型別
最新的 ECMAScript 標準定義了八種資料型別
儘管這些資料型別相對較少,但它們使您能夠對應用程式執行有用的操作。函式是該語言的另一個基本元素。雖然函式在技術上是一種物件,但您可以將物件視為值的命名容器,將函式視為指令碼可以執行的過程。
資料型別轉換
JavaScript 是一種動態型別語言。這意味著您在宣告變數時不必指定其資料型別。這也意味著資料型別在指令碼執行期間會根據需要自動轉換。
例如,您可以按如下方式定義變數
let answer = 42;
之後,您可以將相同的變數賦值為字串值,例如
answer = "Thanks for all the fish!";
因為 JavaScript 是動態型別的,所以此賦值不會導致錯誤訊息。
數字與“+”運算子
在涉及數字和字串值與 + 運算子的表示式中,JavaScript 將數字值轉換為字串。例如,考慮以下語句
x = "The answer is " + 42; // "The answer is 42"
y = 42 + " is the answer"; // "42 is the answer"
z = "37" + 7; // "377"
對於所有其他運算子,JavaScript 不將數字值轉換為字串。例如
"37" - 7; // 30
"37" * 7; // 259
將字串轉換為數字
如果代表數字的值以字串形式儲存在記憶體中,則有多種轉換方法。
parseInt 只返回整數,因此它對小數的使用有所減少。
注意:此外,parseInt 的最佳實踐是始終包含基數引數。基數引數用於指定要使用的數值系統。
parseInt("101", 2); // 5
從字串中檢索數字的另一種方法是使用 +(一元加)運算子。這會隱式執行數字轉換,這與 Number() 函式的過程相同。
"1.1" + "1.1"; // '1.11.1'
(+"1.1") + (+"1.1"); // 2.2
// Note: the parentheses are added for clarity, not required.
字面量
字面量表示 JavaScript 中的值。這些是固定值(不是變數),您在指令碼中字面提供。本節描述以下型別的字面量
陣列字面量
陣列字面量是由零個或多個表示式組成的列表,每個表示式代表一個數組元素,並用方括號 ([]) 括起來。當您使用陣列字面量建立陣列時,它將用指定的值初始化為其元素,並且其 length 設定為指定的引數數量。
以下示例建立了包含三個元素且 length 為三的 coffees 陣列
const coffees = ["French Roast", "Colombian", "Kona"];
每次評估陣列字面量時,它都會建立一個新的陣列物件。例如,在全域性作用域中用字面量定義的陣列在指令碼載入時建立一次。但是,如果陣列字面量在函式內部,則每次呼叫該函式時都會例項化一個新的陣列。
陣列字面量中的多餘逗號
如果您在陣列字面量中連續放置兩個逗號,陣列會為未指定的元素留下一個空槽。以下示例建立了 fish 陣列
const fish = ["Lion", , "Angel"];
當您輸出此陣列時,您將看到
console.log(fish);
// [ 'Lion', <1 empty item>, 'Angel' ]
請注意,第二個專案是“empty”,這與實際的 undefined 值並不完全相同。在使用 Array.prototype.map 等陣列遍歷方法時,空槽會被跳過。但是,透過索引訪問 fish[1] 仍然會返回 undefined。
如果您在元素列表末尾包含一個尾隨逗號,則該逗號將被忽略。
在以下示例中,陣列的 length 為三。沒有 myList[3],並且 myList[1] 為空。列表中的所有其他逗號都表示一個新元素。
const myList = ["home", , "school"];
在以下示例中,陣列的 length 為四,並且 myList[0] 和 myList[2] 缺失。
const myList = [, "home", , "school"];
在以下示例中,陣列的 length 為四,並且 myList[1] 和 myList[3] 缺失。只有最後一個逗號被忽略。
const myList = ["home", , "school", ,];
注意:當您有一個多行陣列時,尾隨逗號有助於保持 git diff 乾淨,因為在末尾新增一個專案只增加一行,而不會修改前一行。
const myList = [
"home",
"school",
+ "hospital",
];
理解多餘逗號的行為對於理解 JavaScript 作為一種語言至關重要。
然而,在編寫自己的程式碼時,您應該明確宣告缺失的元素為 undefined,或者至少插入註釋以突出其缺失。這樣做可以提高程式碼的清晰度和可維護性。
const myList = ["home", /* empty */, "school", /* empty */, ];
布林字面量
布林型別有兩個字面值:true 和 false。
數值字面量
JavaScript 數字字面量包括不同進位制的整數字面量以及十進位制浮點字面量。
請注意,語言規範要求數字字面量是無符號的。儘管如此,像 -123.4 這樣的程式碼片段是正常的,被解釋為應用於數字字面量 123.4 的一元 - 運算子。
整數字面量
整數和 BigInt 字面量可以用十進位制(基數 10)、十六進位制(基數 16)、八進位制(基數 8)和二進位制(基數 2)書寫。
- 十進位制整數字面量是 without 一個前導
0(零)的數字序列。 - 整數字面量的前導
0(零),或前導0o(或0O)表示它是八進位制。八進位制整數字面量只能包含數字0–7。 - 前導
0x(或0X)表示一個十六進位制整數字面量。十六進位制整數可以包含數字(0–9)和字母a–f以及A–F。(字元的大小寫不會改變其值。因此:0xa=0xA=10且0xf=0xF=15。) - 前導
0b(或0B)表示一個二進位制整數字面量。二進位制整數字面量只能包含數字0和1。 - 整數字面量上的尾隨
n字尾表示BigInt字面量。BigInt字面量可以使用上述任何基數。請注意,不允許使用0123n這樣的前導零八進位制語法,但0o123n是可以的。
一些整數字面量的示例是
0, 117, 123456789123456789n (decimal, base 10) 015, 0001, 0o777777777777n (octal, base 8) 0x1123, 0x00111, 0x123456789ABCDEFn (hexadecimal, "hex" or base 16) 0b11, 0b0011, 0b11101001010101010101n (binary, base 2)
有關更多資訊,請參閱詞法語法參考中的數字字面量。
浮點字面量
浮點字面量可以包含以下部分
- 一個無符號十進位制整數,
- 一個小數點 (
.), - 一個小數部分(另一個十進位制數),
- 一個指數。
指數部分是一個 e 或 E,後跟一個整數,該整數可以帶符號(字首 + 或 -)。浮點字面量必須至少有一個數字,並且要麼有一個小數點,要麼有一個 e(或 E)。
更簡潔地說,語法是
[digits].[digits][(E|e)[(+|-)]digits]
例如
3.1415926
.123456789
3.1E+12
.1e-23
物件字面量
物件字面量是一個由零個或多個屬性名及其關聯值組成的列表,用花括號 ({}) 括起來。
警告:不要在語句的開頭使用物件字面量!這會導致錯誤(或行為不如您預期),因為 { 將被解釋為塊的開頭。
以下是一個物件字面量的示例。car 物件的第一個元素定義了一個屬性 myCar,併為其賦值一個新字串 "Saturn";第二個元素 getCar 屬性立即被賦值為呼叫函式 (carTypes("Honda")) 的結果;第三個元素 special 屬性使用了一個現有變數 (sales)。
const sales = "Toyota";
function carTypes(name) {
return name === "Honda" ? name : `Sorry, we don't sell ${name}.`;
}
const car = { myCar: "Saturn", getCar: carTypes("Honda"), special: sales };
console.log(car.myCar); // Saturn
console.log(car.getCar); // Honda
console.log(car.special); // Toyota
此外,您可以將數字或字串字面量用作屬性的名稱,或者將一個物件巢狀在另一個物件中。以下示例使用了這些選項。
const car = { manyCars: { a: "Saab", b: "Jeep" }, 7: "Mazda" };
console.log(car.manyCars.b); // Jeep
console.log(car[7]); // Mazda
物件屬性名稱可以是任何字串,包括空字串。如果屬性名稱不是有效的 JavaScript 識別符號或數字,則必須用引號括起來。
無效識別符號的屬性名稱不能作為點 (.) 屬性訪問。
const unusualPropertyNames = {
"": "An empty string",
"!": "Bang!",
};
console.log(unusualPropertyNames.""); // SyntaxError: Unexpected string
console.log(unusualPropertyNames.!); // SyntaxError: Unexpected token !
相反,它們必須使用方括號表示法 ([]) 訪問。
console.log(unusualPropertyNames[""]); // An empty string
console.log(unusualPropertyNames["!"]); // Bang!
增強的物件字面量
物件字面量支援一系列簡寫語法,包括在構造時設定原型、foo: foo 賦值的簡寫、定義方法、進行 super 呼叫以及使用表示式計算屬性名稱。
總而言之,這些也使物件字面量和類宣告更加接近,並允許基於物件的設計受益於一些相同的便利。
const obj = {
// __proto__
__proto__: theProtoObj,
// Shorthand for 'handler: handler'
handler,
// Methods
toString() {
// Super calls
return `d ${super.toString()}`;
},
// Computed (dynamic) property names
["prop_" + (() => 42)()]: 42,
};
正則表示式字面量
正則表示式字面量(將在稍後詳細定義)是括在斜槓之間的模式。以下是正則表示式字面量的示例。
const re = /ab+c/;
字串字面量
字串字面量是零個或多個字元,用雙引號 (") 或單引號 (') 括起來。字串必須用相同型別的引號(即,要麼都是單引號,要麼都是雙引號)分隔。
以下是字串字面量的示例
'foo'
"bar"
'1234'
'one line \n another line'
"Joyo's cat"
除非您特別需要使用 String 物件,否則應使用字串字面量。有關 String 物件的詳細資訊,請參閱 String。
您可以對字串字面量值呼叫 String 物件的任何方法。JavaScript 會自動將字串字面量轉換為臨時 String 物件,呼叫方法,然後丟棄臨時 String 物件。您還可以將 length 屬性與字串字面量一起使用
// Will print the number of symbols in the string including whitespace.
console.log("Joyo's cat".length); // In this case, 10.
模板字面量也可用。模板字面量用反引號 (`)(重音符)字元而不是雙引號或單引號括起來。
模板字面量為構建字串提供了語法糖。(這類似於 Perl、Python 等語言中的字串插值功能。)
// Basic literal string creation
`In JavaScript '\n' is a line-feed.`;
// Multiline strings
`In JavaScript, template strings can run
over multiple lines, but double and single
quoted strings cannot.`;
// String interpolation
const name = "Lev",
time = "today";
`Hello ${name}, how are you ${time}?`;
帶標籤的模板是一種緊湊的語法,用於指定模板字面量以及呼叫“標籤”函式來解析它。帶標籤的模板只是一種更簡潔、更有語義的方式來呼叫一個處理字串和一組相關值的函式。模板標籤函式的名稱在模板字面量之前——如下例所示,其中模板標籤函式名為 print。print 函式將插入引數並序列化可能出現的任何物件或陣列,從而避免令人討厭的 [object Object]。
const formatArg = (arg) => {
if (Array.isArray(arg)) {
// Print a bulleted list
return arg.map((part) => `- ${part}`).join("\n");
}
if (arg.toString === Object.prototype.toString) {
// This object will be serialized to "[object Object]".
// Let's print something nicer.
return JSON.stringify(arg);
}
return arg;
};
const print = (segments, ...args) => {
// For any well-formed template literal, there will always be N args and
// (N+1) string segments.
let message = segments[0];
segments.slice(1).forEach((segment, index) => {
message += formatArg(args[index]) + segment;
});
console.log(message);
};
const todos = [
"Learn JavaScript",
"Learn Web APIs",
"Set up my website",
"Profit!",
];
const progress = { javascript: 20, html: 50, css: 10 };
print`I need to do:
${todos}
My current progress is: ${progress}
`;
// I need to do:
// - Learn JavaScript
// - Learn Web APIs
// - Set up my website
// - Profit!
// My current progress is: {"javascript":20,"html":50,"css":10}
由於帶標籤的模板字面量只是函式呼叫的語法糖,您可以將上述內容重寫為等效的函式呼叫
print(["I need to do:\n", "\nMy current progress is: ", "\n"], todos, progress);
這可能讓人聯想到 console.log 風格的插值
console.log("I need to do:\n%o\nMy current progress is: %o\n", todos, progress);
您可以看到帶標籤的模板比傳統的“格式化器”函式更自然,在後者中,變數和模板本身必須單獨宣告。
在字串中使用特殊字元
除了普通字元之外,您還可以在字串中包含特殊字元,如下例所示。
"one line \n another line";
下表列出了您可以在 JavaScript 字串中使用的特殊字元。
| 字元 | 含義 |
|---|---|
\0 |
空位元組 |
\b |
退格鍵 |
\f |
換頁符 |
\n |
換行符 |
\r |
回車符 |
\t |
製表符 |
\v |
垂直製表符 |
\' |
撇號或單引號 |
\" |
雙引號 |
\\ |
反斜槓字元 |
\XXX |
由最多三個八進位制數字 XXX(介於 0 和 377 之間)指定的帶有 Latin-1 編碼的字元。例如,\251 是版權符號的八進位制序列。 |
\xXX |
由兩個十六進位制數字 XX(介於 00 和 FF 之間)指定的帶有 Latin-1 編碼的字元。例如,\xA9 是版權符號的十六進位制序列。 |
\uXXXX |
由四個十六進位制數字 XXXX 指定的 Unicode 字元。例如,\u00A9 是版權符號的 Unicode 序列。請參閱Unicode 轉義序列。 |
\u{XXXXX} |
Unicode 碼點轉義。例如,\u{2F804} 與 Unicode 轉義 \uD87E\uDC04 相同。 |
跳脫字元
對於表中未列出的字元,前導反斜槓將被忽略,但這種用法已棄用,應避免使用。
您可以透過在引號前加上反斜槓來在字串中插入引號。這被稱為轉義引號。例如
const quote = "He read \"The Cremation of Sam McGee\" by R.W. Service.";
console.log(quote);
結果將是
He read "The Cremation of Sam McGee" by R.W. Service.
要在字串中包含字面反斜槓,您必須轉義反斜槓字元。例如,要將檔案路徑 c:\temp 賦值給字串,請使用以下內容
const home = "c:\\temp";
您還可以透過在換行符前加上反斜槓來轉義換行符。反斜槓和換行符都從字串的值中移除。
const str =
"this string \
is broken \
across multiple \
lines.";
console.log(str); // this string is broken across multiple lines.
更多資訊
本章側重於宣告和型別的基本語法。要了解更多關於 JavaScript 語言結構的資訊,另請參閱本指南中的以下章節
在下一章中,我們將瞭解控制流結構和錯誤處理。