JavaScript 語言概述

JavaScript 是一種多正規化動態語言,它具有型別和運算子、標準內建物件和方法。其語法基於 Java 和 C 語言 - 這些語言中的許多結構也適用於 JavaScript。JavaScript 支援使用 物件原型 和類進行面向物件程式設計。它還支援函數語言程式設計,因為函式是 一等公民 物件,可以透過表示式輕鬆建立並像任何其他物件一樣傳遞。

此頁面簡要概述了各種 JavaScript 語言功能,專為熟悉其他語言(如 C 或 Java)的讀者撰寫。

資料型別

讓我們從任何語言的構建塊開始:型別。JavaScript 程式操作值,而這些值都屬於某種型別。JavaScript 提供了七種基本型別

  • 數字: 用於所有數字值(整數和浮點數),除了非常大的整數。
  • BigInt: 用於任意大的整數。
  • 字串: 用於儲存文字。
  • 布林值: truefalse - 通常用於條件邏輯。
  • 符號: 用於建立不會發生衝突的唯一識別符號。
  • 未定義: 表示變數尚未賦值。
  • : 表示一個故意非值。

其他所有內容都稱為 物件。常見的物件型別包括

函式在 JavaScript 中不是特殊的資料結構 - 它們只是可以呼叫的特殊型別的物件。

數字

JavaScript 有兩種內建的數字型別:Number 和 BigInt。

Number 型別是 IEEE 754 64 位雙精度浮點數,這意味著整數可以在 -(253 − 1)253 − 1 之間安全地表示,而不會損失精度,浮點數可以儲存到 1.79 × 10308。在數字中,JavaScript 不區分浮點數和整數。

js
console.log(3 / 2); // 1.5, not 1

因此,明顯的整數實際上是隱式的浮點數。由於 IEEE 754 編碼,有時浮點運算可能不精確。

js
console.log(0.1 + 0.2); // 0.30000000000000004

對於需要整數的運算(例如按位運算),該數字將轉換為 32 位整數。

數字字面量 也可以有字首來指示基數(二進位制、八進位制、十進位制或十六進位制),或指數字尾。

js
console.log(0b111110111); // 503
console.log(0o767); // 503
console.log(0x1f7); // 503
console.log(5.03e2); // 503

BigInt 型別是任意長度的整數。它的行為類似於 C 的整數型別(例如,除法截斷為零),只是它可以無限增長。BigInt 使用數字字面量和 n 字尾指定。

js
console.log(-3n / 2n); // -1n

支援標準的 算術運算子,包括加法、減法、餘數運算等。BigInt 和數字不能在算術運算中混合使用。

Math 物件提供標準的數學函式和常量。

js
Math.sin(3.5);
const circumference = 2 * Math.PI * r;

將字串轉換為數字有三種方法

  • parseInt(),它解析字串以獲取整數。
  • parseFloat(),它解析字串以獲取浮點數。
  • Number() 函式,它解析字串,就好像它是一個數字字面量,並支援許多不同的數字表示。

你也可以使用 一元加號 + 作為 Number() 的簡寫。

數字值還包括 NaN(“非數字”的縮寫)和 Infinity。許多“無效數學”運算將返回 NaN - 例如,如果嘗試解析非數字字串,或者對負值使用 Math.log()。除以零會產生 Infinity(正或負)。

NaN 是具有傳染性的: 如果你將其作為運算元提供給任何數學運算,結果也將是 NaNNaN 是 JavaScript 中唯一一個與其自身不相等的值(根據 IEEE 754 規範)。

字串

JavaScript 中的字串是 Unicode 字元序列。對於那些不得不處理國際化的人來說,這應該是個好訊息。更準確地說,它們是 UTF-16 編碼的

js
console.log("Hello, world");
console.log("你好,世界!"); // Nearly all Unicode characters can be written literally in string literals

字串可以使用單引號或雙引號編寫 - JavaScript 沒有區分字元和字串。如果你想表示單個字元,只需使用包含該單個字元的字串。

js
console.log("Hello"[1] === "e"); // true

要查詢字串的長度(以 程式碼單元 為單位),請訪問其 length 屬性。

字串具有 實用方法 來操作字串和訪問有關字串的資訊。因為所有基元在設計上都是不可變的,所以這些方法會返回新的字串。

+ 運算子對字串進行了過載: 當其中一個運算元是字串時,它執行字串連線而不是數字加法。特殊的 模板字面量 語法允許你更簡潔地編寫包含嵌入式表示式的字串。與 Python 的 f-字串或 C# 的插值字串不同,模板字面量使用反引號(而不是單引號或雙引號)。

js
const age = 25;
console.log("I am " + age + " years old."); // String concatenation
console.log(`I am ${age} years old.`); // Template literal

其他型別

JavaScript 區分 null,它表示故意非值(並且只能透過 null 關鍵字訪問),以及 undefined,它表示值不存在。獲得 undefined 的方法有很多

  • return 語句沒有值 (return;) 會隱式返回 undefined
  • 訪問不存在的 物件 屬性 (obj.iDontExist) 會返回 undefined
  • 變數宣告沒有初始化 (let x;) 會隱式將變數初始化為 undefined

JavaScript 具有布林型別,可能的值為 truefalse - 它們都是關鍵字。任何值都可以根據以下規則轉換為布林值

  1. false0、空字串 ("")、NaNnullundefined 都變為 false
  2. 所有其他值都變為 true

你可以使用 Boolean() 函式顯式執行此轉換

js
Boolean(""); // false
Boolean(234); // true

但是,這很少必要,因為 JavaScript 會在它期望布林值時靜默執行此轉換,例如在 if 語句中(參見 控制結構)。出於這個原因,我們有時會說“真值”和“假值”,意味著在布林值上下文中分別變為 truefalse 的值。

支援布林運算,例如 &&(邏輯)、||(邏輯)和 !(邏輯);請參閱 運算子

Symbol 型別通常用於建立唯一的識別符號。使用 Symbol() 函式建立的每個符號都保證是唯一的。此外,還有一些註冊符號,它們是共享常量,以及眾所周知的符號,它們被語言用作某些操作的“協議”。您可以在 符號參考 中閱讀有關它們的更多資訊。

變數

JavaScript 中的變數使用三個關鍵字之一宣告:letconstvar

let 允許您宣告塊級變數。宣告的變數從它所包含的中可用。

js
let a;
let name = "Simon";

// myLetVariable is *not* visible out here

for (let myLetVariable = 0; myLetVariable < 5; myLetVariable++) {
  // myLetVariable is only visible in here
}

// myLetVariable is *not* visible out here

const 允許您宣告值永遠不會改變的變數。該變數從它被宣告的中可用。

js
const Pi = 3.14; // Declare variable Pi
console.log(Pi); // 3.14

const 宣告的變數不能重新賦值。

js
const Pi = 3.14;
Pi = 1; // will throw an error because you cannot change a constant variable.

const 宣告只阻止重新賦值,如果它是物件,它們不會阻止修改變數的值。

js
const obj = {};
obj.a = 1; // no error
console.log(obj); // { a: 1 }

var 宣告可能具有令人驚訝的行為(例如,它們不是塊級作用域的),並且在現代 JavaScript 程式碼中不建議使用。

如果您宣告一個變數而不為它賦值,它的值將是 undefined。您不能在沒有初始化程式的情況下宣告 const 變數,因為您以後無法更改它。

letconst 宣告的變數仍然佔據它們定義的整個作用域,並且在實際宣告行之前處於稱為 暫時死區 的區域中。這與變數遮蔽有一些有趣的互動,而在其他語言中不會發生。

js
function foo(x, condition) {
  if (condition) {
    console.log(x);
    const x = 2;
    console.log(x);
  }
}

foo(1, true);

在大多數其他語言中,這將記錄 “1” 和 “2”,因為在 const x = 2 行之前,x 應該仍然引用上層作用域中的引數 x。在 JavaScript 中,由於每個宣告佔據整個作用域,這將在第一個 console.log 上丟擲錯誤:“無法在初始化之前訪問 'x'”。有關更多資訊,請參閱 let 的參考頁面。

JavaScript 是動態型別的。型別(如 上一節 中所述)僅與值相關聯,而不與變數相關聯。對於 let 宣告的變數,您可以始終透過重新賦值來更改其型別。

js
let a = 1;
a = "foo";

運算子

JavaScript 的數字運算子包括 +-*/%(餘數)和 **(指數)。值使用 = 賦值。每個二元運算子也有一個複合賦值對應項,例如 +=-=,它們擴充套件到 x = x operator y

js
x += 5;
x = x + 5;

您可以使用 ++-- 分別遞增和遞減。這些可以用作字首或字尾運算子。

+ 運算子 也可以進行字串連線

js
"hello" + " world"; // "hello world"

如果您將字串新增到數字(或其他值),所有內容將首先轉換為字串。這可能會讓您感到困惑

js
"3" + 4 + 5; // "345"
3 + 4 + "5"; // "75"

將空字串新增到某物是將其本身轉換為字串的一種有用方法。

JavaScript 中的比較 可以使用 <><=>= 進行,這些操作對字串和數字都有效。對於相等性,雙等號運算子 如果您提供不同的型別,會執行型別強制轉換,有時會得到有趣的結果。另一方面,三等號運算子 不會嘗試型別強制轉換,通常是首選。

js
123 == "123"; // true
1 == true; // true

123 === "123"; // false
1 === true; // false

雙等號和三等號也有它們的不等價對應項:!=!==

JavaScript 還具有 按位運算子邏輯運算子。值得注意的是,邏輯運算子不僅對布林值有效,而且它們根據值的“真值”起作用。

js
const a = 0 && "Hello"; // 0 because 0 is "falsy"
const b = "Hello" || "world"; // "Hello" because both "Hello" and "world" are "truthy"

&&|| 運算子使用短路邏輯,這意味著它們是否將執行其第二個運算元取決於第一個運算元。這對於在訪問其屬性之前檢查空物件很有用

js
const name = o && o.getName();

或用於快取值(當假值無效時)

js
const name = cachedName || (cachedName = getName());

有關運算子的完整列表,請參閱 指南頁面參考部分。您可能對 運算子優先順序 特別感興趣。

語法

JavaScript 語法與 C 家族非常相似。有一些值得一提的要點

  • 識別符號 可以包含 Unicode 字元,但不能是 保留字 之一。
  • 註釋 通常是 ///* */,而許多其他指令碼語言(如 Perl、Python 和 Bash)使用 #
  • 在 JavaScript 中分號是可選的——該語言在需要時會自動插入它們。但是,有一些需要注意的注意事項,因為與 Python 不同,分號仍然是語法的一部分。

有關 JavaScript 語法的深入瞭解,請參閱 詞法語法參考頁面

控制結構

JavaScript 具有與 C 家族中其他語言類似的一組控制結構。條件語句由 ifelse 支援;您可以將它們連結在一起

js
let name = "kittens";
if (name === "puppies") {
  name += " woof";
} else if (name === "kittens") {
  name += " meow";
} else {
  name += "!";
}
name === "kittens meow";

JavaScript 沒有 elif,而 else if 實際上只是一個由單個 if 語句組成的 else 分支。

JavaScript 具有 while 迴圈和 do...while 迴圈。第一個適用於基本迴圈;第二個適用於您希望確保迴圈體至少執行一次的迴圈

js
while (true) {
  // an infinite loop!
}

let input;
do {
  input = get_input();
} while (inputIsNotValid(input));

JavaScript 的 for 迴圈 與 C 和 Java 中的迴圈相同:它允許您在一行中提供迴圈的控制資訊。

js
for (let i = 0; i < 5; i++) {
  // Will execute 5 times
}

JavaScript 還包含另外兩種突出的 for 迴圈:for...of,它遍歷 可迭代物件,最顯著的是陣列,以及 for...in,它訪問物件的所有 可列舉 屬性。

js
for (const value of array) {
  // do something with value
}

for (const property in object) {
  // do something with object property
}

switch 語句可用於根據相等性檢查進行多個分支。

js
switch (action) {
  case "draw":
    drawIt();
    break;
  case "eat":
    eatIt();
    break;
  default:
    doNothing();
}

類似於 C,case 子句在概念上與 標籤 相同,因此如果您不新增 break 語句,執行將“貫穿”到下一級。但是,它們實際上不是跳轉表——任何表示式都可以是 case 子句的一部分,而不僅僅是字串或數字字面量,並且它們將逐個進行評估,直到一個等於正在匹配的值。比較使用 === 運算子在兩者之間進行。

與 Rust 等一些語言不同,控制流結構在 JavaScript 中是語句,這意味著您不能將它們賦值給變數,例如 const a = if (x) { 1 } else { 2 }

JavaScript 錯誤使用 try...catch 語句處理。

js
try {
  buildMySite("./website");
} catch (e) {
  console.error("Building site failed:", e);
}

可以使用 throw 語句丟擲錯誤。許多內建操作也可能丟擲錯誤。

js
function buildMySite(siteDirectory) {
  if (!pathExists(siteDirectory)) {
    throw new Error("Site directory does not exist");
  }
}

通常,您無法判斷剛剛捕獲的錯誤的型別,因為任何東西都可以從 throw 語句中丟擲。但是,您通常可以假設它是一個 Error 例項,如上面的示例所示。Error 有些內建子類,例如 TypeErrorRangeError,您可以使用它們來提供有關錯誤的額外語義。JavaScript 中沒有條件捕獲——如果您只想處理一種型別的錯誤,您需要捕獲所有內容,使用 instanceof 識別錯誤型別,然後重新丟擲其他情況。

js
try {
  buildMySite("./website");
} catch (e) {
  if (e instanceof RangeError) {
    console.error("Seems like a parameter is out of range:", e);
    console.log("Retrying...");
    buildMySite("./website");
  } else {
    // Don't know how to handle other error types; throw them so
    // something else up in the call stack may catch and handle it
    throw e;
  }
}

如果任何 try...catch 在呼叫堆疊中沒有捕獲錯誤,程式將退出。

有關控制流語句的完整列表,請參閱 參考部分

物件

JavaScript 物件可以被認為是鍵值對的集合。因此,它們類似於

  • Python 中的字典。
  • Perl 和 Ruby 中的雜湊。
  • C 和 C++ 中的雜湊表。
  • Java 中的 HashMap。
  • PHP 中的關聯陣列。

JavaScript 物件是雜湊。與靜態型別語言中的物件不同,JavaScript 中的物件沒有固定的形狀——屬性可以隨時新增、刪除、重新排序、修改或動態查詢。物件鍵始終是 字串符號——即使陣列索引(通常是整數),實際上在幕後也是字串。

物件通常使用字面量語法建立

js
const obj = {
  name: "Carrot",
  for: "Max",
  details: {
    color: "orange",
    size: 12,
  },
};

物件屬性可以使用點 (.) 或方括號 ([]) 進行 訪問。使用點表示法時,鍵必須是有效的 識別符號。另一方面,方括號允許使用動態鍵值對物件進行索引。

js
// Dot notation
obj.name = "Simon";
const name = obj.name;

// Bracket notation
obj["name"] = "Simon";
const name = obj["name"];

// Can use a variable to define a key
const userName = prompt("what is your key?");
obj[userName] = prompt("what is its value?");

屬性訪問可以連結在一起

js
obj.details.color; // orange
obj["details"]["size"]; // 12

物件始終是引用,因此除非明確複製物件,否則對物件的修改將對外部可見。

js
const obj = {};
function doSomething(o) {
  o.x = 1;
}
doSomething(obj);
console.log(obj.x); // 1

這也意味著兩個單獨建立的物件將永遠不相等 (!==),因為它們是不同的引用。如果您持有同一個物件的兩個引用,修改其中一個將透過另一個進行觀察。

js
const me = {};
const stillMe = me;
me.x = 1;
console.log(stillMe.x); // 1

有關物件和原型的更多資訊,請參閱 Object 參考頁面。有關物件初始化器語法的更多資訊,請參閱其 參考頁面

本頁省略了有關物件原型和繼承的所有詳細資訊,因為您通常可以使用 來實現繼承,而無需觸及底層機制(您可能聽說過這很深奧)。要了解有關它們的資訊,請參閱 繼承和原型鏈

陣列

JavaScript 中的陣列實際上是一種特殊型別的物件。它們的工作方式非常類似於普通物件(數字屬性自然只能使用 [] 語法訪問),但它們有一個名為 length 的神奇屬性。這始終比陣列中的最高索引多 1。

陣列通常使用陣列字面量建立

js
const a = ["dog", "cat", "hen"];
a.length; // 3

JavaScript 陣列仍然是物件——您可以為它們分配任何屬性,包括任意數字索引。唯一的“神奇”之處在於,當您設定特定索引時,length 會自動更新。

js
const a = ["dog", "cat", "hen"];
a[100] = "fox";
console.log(a.length); // 101
console.log(a); // ['dog', 'cat', 'hen', empty × 97, 'fox']

我們上面得到的陣列被稱為 稀疏陣列,因為中間存在未佔用的插槽,這會導致引擎將其從陣列降級為雜湊表。確保您的陣列是密集填充的!

越界索引不會丟擲異常。如果您查詢一個不存在的陣列索引,您將獲得一個 undefined 值作為返回值。

js
const a = ["dog", "cat", "hen"];
console.log(typeof a[90]); // undefined

陣列可以包含任何元素,並且可以任意增長或縮小。

js
const arr = [1, "foo", true];
arr.push({});
// arr = [1, "foo", true, {}]

您可以使用 for 迴圈迭代陣列,就像您在其他類似 C 的語言中那樣。

js
for (let i = 0; i < a.length; i++) {
  // Do something with a[i]
}

或者,由於陣列是可迭代的,您可以使用 for...of 迴圈,它等同於 C++/Java 的 for (int x : arr) 語法。

js
for (const currentValue of a) {
  // Do something with currentValue
}

陣列附帶了大量的 陣列方法。其中許多方法會迭代陣列 - 例如,map() 會對每個陣列元素應用一個回撥函式,並返回一個新的陣列。

js
const babies = ["dog", "cat", "hen"].map((name) => `baby ${name}`);
// babies = ['baby dog', 'baby cat', 'baby hen']

函式

與物件一樣,函式是理解 JavaScript 的核心組成部分。最基本的函式宣告如下所示:

js
function add(x, y) {
  const total = x + y;
  return total;
}

JavaScript 函式可以接受 0 個或多個引數。函式體可以包含任意數量的語句,並且可以宣告自己的變數,這些變數是該函式的區域性變數。return 語句可用於在任何時間返回一個值,並終止函式。如果沒有使用 return 語句(或沒有值的空 return),JavaScript 會返回 undefined

函式可以呼叫比它指定的引數多或少的引數。如果您在沒有傳遞函式期望的引數的情況下呼叫函式,它們將被設定為 undefined。如果您傳遞的引數比它期望的多,函式將忽略多餘的引數。

js
add(); // NaN
// Equivalent to add(undefined, undefined)

add(2, 3, 4); // 5
// added the first two; 4 was ignored

存在許多其他引數語法可用。例如,剩餘引數語法 允許將呼叫者傳遞的所有額外引數收集到一個數組中,類似於 Python 的 *args。(由於 JS 在語言級別沒有命名引數,所以沒有 **kwargs。)

js
function avg(...args) {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
}

avg(2, 3, 4, 5); // 3.5

在上面的程式碼中,變數 args 儲存傳遞給函式的所有值。

剩餘引數將儲存在其聲明後的所有引數,而不是之前的引數。換句話說,function avg(firstValue, ...args) 將儲存傳遞給函式的第一個值在 firstValue 變數中,其餘引數在 args 中。

如果函式接受一個引數列表,並且您已經將它們儲存在一個數組中,您可以在函式呼叫中使用 擴充套件語法 將陣列擴充套件為元素列表。例如:avg(...numbers)

我們提到過 JavaScript 沒有命名引數。但是,可以使用 物件解構 來實現它們,它允許物件以方便的方式打包和解包。

js
// Note the { } braces: this is destructuring an object
function area({ width, height }) {
  return width * height;
}

// The { } braces here create a new object
console.log(area({ width: 2, height: 3 }));

還有 預設引數 語法,它允許省略的引數(或那些作為 undefined 傳遞的引數)具有預設值。

js
function avg(firstValue, secondValue, thirdValue = 0) {
  return (firstValue + secondValue + thirdValue) / 3;
}

avg(1, 2); // 1, instead of NaN

匿名函式

JavaScript 允許您建立匿名函式 - 也就是說,沒有名稱的函式。在實踐中,匿名函式通常用作其他函式的引數,立即分配給一個變數,該變數可用於呼叫函式,或從另一個函式返回。

js
// Note that there's no function name before the parentheses
const avg = function (...args) {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
};

這使得匿名函式可以透過使用一些引數呼叫 avg() 來呼叫 - 也就是說,它在語義上等同於使用 function avg() {} 宣告語法宣告函式。

還有另一種定義匿名函式的方法 - 使用 箭頭函式表示式

js
// Note that there's no function name before the parentheses
const avg = (...args) => {
  let sum = 0;
  for (const item of args) {
    sum += item;
  }
  return sum / args.length;
};

// You can omit the `return` when simply returning an expression
const sum = (a, b, c) => a + b + c;

箭頭函式在語義上不等同於函式表示式 - 有關更多資訊,請參見其 參考頁面

匿名函式還有另一種有用的方式:它可以在單個表示式中同時宣告和呼叫,稱為 立即執行函式表示式 (IIFE)

js
(function () {
  // …
})();

有關 IIFE 的用例,您可以閱讀 使用閉包模擬私有方法

遞迴函式

JavaScript 允許您遞迴呼叫函式。這對於處理樹結構特別有用,例如在瀏覽器 DOM 中找到的那些。

js
function countChars(elm) {
  if (elm.nodeType === 3) {
    // TEXT_NODE
    return elm.nodeValue.length;
  }
  let count = 0;
  for (let i = 0, child; (child = elm.childNodes[i]); i++) {
    count += countChars(child);
  }
  return count;
}

函式表示式也可以命名,這使得它們可以遞迴。

js
const charsInBody = (function counter(elm) {
  if (elm.nodeType === 3) {
    // TEXT_NODE
    return elm.nodeValue.length;
  }
  let count = 0;
  for (let i = 0, child; (child = elm.childNodes[i]); i++) {
    count += counter(child);
  }
  return count;
})(document.body);

如上所述提供給函式表示式的名稱僅對函式自身的作用域可用。這允許引擎進行更多最佳化,並導致更易讀的程式碼。該名稱也會出現在偵錯程式和一些堆疊跟蹤中,這可以節省您除錯時間。

如果您習慣於函數語言程式設計,請注意 JavaScript 中遞迴的效能影響。雖然語言規範指定了 尾呼叫最佳化,但由於恢復堆疊跟蹤和可除錯性的困難,只有 JavaScriptCore(由 Safari 使用)實現了它。對於深度遞迴,請考慮使用迭代來避免堆疊溢位。

函式是一等公民

JavaScript 函式是一等公民。這意味著它們可以分配給變數,作為引數傳遞給其他函式,並從其他函式返回。此外,JavaScript 支援 閉包,無需顯式捕獲,使您可以方便地應用函數語言程式設計風格。

js
// Function returning function
const add = (x) => (y) => x + y;
// Function accepting function
const babies = ["dog", "cat", "hen"].map((name) => `baby ${name}`);

請注意,JavaScript 函式本身是物件 - 與 JavaScript 中的所有其他內容一樣 - 您可以像我們在前面“物件”部分中看到的那樣,在它們上新增或更改屬性。

內部函式

JavaScript 函式宣告允許在其他函式內部。JavaScript 中巢狀函式的一個重要細節是它們可以訪問其父函式作用域中的變數。

js
function parentFunc() {
  const a = 1;

  function nestedFunc() {
    const b = 4; // parentFunc can't use this
    return a + b;
  }
  return nestedFunc(); // 5
}

這為編寫更易於維護的程式碼提供了極大的實用性。如果一個被呼叫的函式依賴於一個或兩個對程式碼的其他部分沒有用的函式,您可以將這些實用函式巢狀在其中。這減少了全域性作用域中的函式數量。

這也是對全域性變數誘惑力的一個很好的對策。在編寫複雜程式碼時,通常會傾向於使用全域性變數在多個函式之間共享值,這會導致難以維護的程式碼。巢狀函式可以在其父級中共享變數,因此您可以使用這種機制將函式耦合在一起,而不會汙染全域性名稱空間。

JavaScript 提供了 語法,與 Java 等語言非常相似。

js
class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    return `Hello, I'm ${this.name}!`;
  }
}

const p = new Person("Maria");
console.log(p.sayHello());

JavaScript 類只是必須使用 new 運算子例項化的函式。每次例項化類時,它都會返回一個包含類指定的函式和屬性的物件。類不會強制執行任何程式碼組織 - 例如,您可以有函式返回類,或者您可以在每個檔案中擁有多個類。以下是如何隨意建立類的示例:它只是從箭頭函式返回的表示式。這種模式被稱為 mixin

js
const withAuthentication = (cls) =>
  class extends cls {
    authenticate() {
      // …
    }
  };

class Admin extends withAuthentication(Person) {
  // …
}

靜態屬性透過在前面加上 static 來建立。私有屬性透過在前面加上一個雜湊 # (而不是 private)來建立。雜湊是屬性名稱的組成部分。(將 # 視為 Python 中的 _。)與大多數其他語言不同,絕對沒有辦法在類體外部讀取私有屬性 - 即使在派生類中也不行。

有關各種類功能的詳細指南,您可以閱讀 指南頁面

非同步程式設計

JavaScript 本質上是單執行緒的。沒有 並行性;只有 併發性。非同步程式設計由 事件迴圈 提供支援,它允許將一組任務排隊並輪詢完成。

在 JavaScript 中編寫非同步程式碼有三種慣用方式。

例如,以下是 JavaScript 中檔案讀取操作可能的樣子

js
// Callback-based
fs.readFile(filename, (err, content) => {
  // This callback is invoked when the file is read, which could be after a while
  if (err) {
    throw err;
  }
  console.log(content);
});
// Code here will be executed while the file is waiting to be read

// Promise-based
fs.readFile(filename)
  .then((content) => {
    // What to do when the file is read
    console.log(content);
  })
  .catch((err) => {
    throw err;
  });
// Code here will be executed while the file is waiting to be read

// Async/await
async function readFile(filename) {
  const content = await fs.readFile(filename);
  console.log(content);
}

核心語言沒有指定任何非同步程式設計功能,但在與外部環境互動時至關重要 - 從 詢問使用者許可權獲取資料,再到 讀取檔案。保持可能長時間執行的操作非同步可以確保其他程序在等待時仍然可以執行 - 例如,瀏覽器在等待使用者單擊按鈕以授予許可權時不會凍結。

如果您有一個非同步值,則無法同步獲取其值。例如,如果您有一個 Promise,您只能透過 then() 方法訪問最終結果。類似地,await 只能在非同步上下文中使用,通常是非同步函式或模組。Promise 從不阻塞 - 只有依賴 Promise 結果的邏輯將被延遲;其他所有內容將繼續執行。如果您是函數語言程式設計人員,您可能會將 Promise 識別為 單子,它可以用 then() 對映(但是,它們不是真正的單子,因為它們會自動扁平化;即您不能擁有 Promise<Promise<T>>)。

事實上,單執行緒模型使 Node.js 成為伺服器端程式設計的熱門選擇,因為它具有非阻塞 I/O,這使得處理大量資料庫或檔案系統請求的效能非常高。但是,純 JavaScript 的 CPU 密集型(計算密集型)任務仍然會阻塞主執行緒。要實現真正的並行化,您可能需要使用 工作執行緒

要了解更多關於非同步程式設計的資訊,您可以閱讀有關 使用 Promise 的資訊,或遵循 非同步 JavaScript 教程。

模組

JavaScript 還指定了大多數執行時支援的模組系統。模組通常是一個檔案,由其檔案路徑或 URL 標識。您可以使用 importexport 語句在模組之間交換資料。

js
import { foo } from "./foo.js";

// Unexported variables are local to the module
const b = 2;

export const a = 1;

與 Haskell、Python、Java 等不同,JavaScript 模組解析完全由主機定義 - 它通常基於 URL 或檔案路徑,因此相對檔案路徑“按預期工作”,並且相對於當前模組的路徑,而不是某個專案根路徑。

然而,JavaScript 語言不提供標準庫模組 - 所有核心功能都由全域性變數提供支援,例如 MathIntl。 這是由於 JavaScript 長期以來缺乏模組系統,以及採用模組系統需要對執行時設定進行一些更改。

不同的執行時可能使用不同的模組系統。 例如,Node.js 使用包管理器 npm 並且主要基於檔案系統,而 Deno 和瀏覽器完全基於 URL,並且模組可以從 HTTP URL 解析。

有關更多資訊,請參閱 模組指南頁面

語言和執行時

在整個頁面中,我們一直提到某些功能是語言級的,而其他功能是執行時級的。

JavaScript 是一種通用指令碼語言。 核心語言規範 側重於純粹的計算邏輯。 它不處理任何輸入/輸出 - 事實上,如果沒有額外的執行時級 API(最值得注意的是 console.log()),JavaScript 程式的行為是完全不可觀察的。

執行時或主機是為 JavaScript 引擎(直譯器)提供資料、提供額外的全域性屬性以及為引擎提供與外部世界互動的鉤子的東西。 模組解析、讀取資料、列印訊息、傳送網路請求等都是執行時級操作。 自誕生以來,JavaScript 已被採用在各種環境中,例如瀏覽器(提供 DOM 等 API)、Node.js(提供 檔案系統訪問 等 API)等等。 JavaScript 已成功整合到 Web(這是其主要用途)、移動應用程式、桌面應用程式、伺服器端應用程式、無伺服器、嵌入式系統等中。 當您瞭解 JavaScript 核心功能時,瞭解主機提供的功能也很重要,以便將知識付諸實踐。 例如,您可以瞭解所有 Web 平臺 API,它們由瀏覽器實現,有時也由非瀏覽器實現。

進一步探索

此頁面提供了對各種 JavaScript 功能與其他語言的比較的非常基本的瞭解。 如果您想了解更多關於語言本身以及每個功能的細微差別,您可以閱讀 JavaScript 指南JavaScript 參考

由於空間和複雜性,我們省略了語言中的一些基本部分,但您可以自行探索