let

Baseline 已廣泛支援

此特性已非常成熟,可在多種裝置和瀏覽器版本上使用。自 ⁨2016 年 9 月⁩以來,它已在各大瀏覽器中可用。

let 宣告用於宣告可重新賦值的塊級作用域區域性變數,可以選擇將其初始化為一個值。

試一試

let x = 1;

if (x === 1) {
  let x = 2;

  console.log(x);
  // Expected output: 2
}

console.log(x);
// Expected output: 1

語法

js
let name1;
let name1 = value1;
let name1 = value1, name2 = value2;
let name1, name2 = value2;
let name1 = value1, name2, /* …, */ nameN = valueN;

引數

nameN

要宣告的變數名。每個變數名都必須是合法的 JavaScript 識別符號解構繫結模式

valueN 可選

變數的初始值。它可以是任何合法的表示式。預設值是 undefined

描述

使用 let 宣告的變數的作用域是以下最接近包含 let 宣告的由大括號括起來的語法之一:

或者如果以上都不適用:

  • 對於在模組模式下執行的程式碼,為當前模組
  • 對於在指令碼模式下執行的程式碼,為全域性作用域。

var 相比,let 宣告具有以下區別:

  • let 宣告的作用域是塊以及函式。

  • let 宣告只能在宣告位置被執行到之後才能訪問(參見暫時性死區)。因此,let 宣告通常被認為是不可提升的。

  • 當在指令碼的頂層宣告時,let 宣告不會在 globalThis 上建立屬性。

  • let 宣告不能在同一作用域內被任何其他宣告重新宣告

  • let 開啟的是_宣告_,而不是_語句_。這意味著你不能將獨立的 let 宣告用作塊的主體(這是有道理的,因為沒有辦法訪問該變數)。

    js
    if (true) let a = 1; // SyntaxError: Lexical declaration cannot appear in a single-statement context
    

請注意,在非嚴格模式下,當使用 varfunction 宣告時,let 允許作為識別符號名稱,但你應該避免使用 let 作為識別符號名稱,以防止出現意外的語法歧義。

許多程式碼風格指南(包括 MDN 的)建議,只要變數在其作用域內不被重新賦值,就使用 const 而不是 let。這使得意圖明確,即變數的型別(或原始值的值)永遠不會改變。其他人可能更喜歡對被修改的非原始值使用 let

let 關鍵字後面的列表稱為_繫結列表_,並用逗號分隔,其中逗號_不是_逗號運算子= 符號_不是_賦值運算子。列表中後面變數的初始化器可以引用列表中較早的變數。

暫時性死區 (TDZ)

使用 letconstclass 宣告的變數從塊的開始到程式碼執行到達變數宣告和初始化的地方,都被認為處於“暫時性死區”(TDZ)中。

在 TDZ 內部,變數尚未用值初始化,任何嘗試訪問它的行為都將導致 ReferenceError。當執行到達程式碼中宣告變數的位置時,變數將用值初始化。如果變數宣告時未指定初始值,它將用 undefined 值初始化。

這與 var 變數不同,var 變數如果在宣告之前訪問,將返回 undefined 值。下面的程式碼演示了在宣告位置之前訪問 letvar 時,程式碼中結果的不同。

js
{
  // TDZ starts at beginning of scope
  console.log(bar); // "undefined"
  console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
  var bar = 1;
  let foo = 2; // End of TDZ (for foo)
}

使用“暫時性”一詞是因為該區域取決於執行順序(時間),而不是程式碼編寫順序(位置)。例如,下面的程式碼有效,因為儘管使用 let 變數的函數出現在變數宣告之前,但該函式是在 TDZ 之外_呼叫_的。

js
{
  // TDZ starts at beginning of scope
  const func = () => console.log(letVar); // OK

  // Within the TDZ letVar access throws `ReferenceError`

  let letVar = 3; // End of TDZ (for letVar)
  func(); // Called outside TDZ!
}

對 TDZ 中的變數使用 typeof 運算子會丟擲 ReferenceError

js
{
  typeof i; // ReferenceError: Cannot access 'i' before initialization
  let i = 10;
}

這與對未宣告變數和值為 undefined 的變數使用 typeof 不同。

js
console.log(typeof undeclaredVariable); // "undefined"

注意: letconst 宣告僅在當前指令碼被處理時才會被處理。如果您在同一 HTML 中有兩個以指令碼模式執行的 <script> 元素,則第一個指令碼不受第二個指令碼中宣告的頂級 letconst 變數的 TDZ 限制,但如果您在第一個指令碼中聲明瞭一個 letconst 變數,在第二個指令碼中再次宣告它將導致重新宣告錯誤

重新宣告

let 宣告不能與任何其他宣告在同一作用域內,包括 letconstclassfunctionvarimport 宣告。

js
{
  let foo;
  let foo; // SyntaxError: Identifier 'foo' has already been declared
}

函式體內的 let 宣告不能與引數同名。catch 塊內的 let 宣告不能與 catch 繫結的識別符號同名。

js
function foo(a) {
  let a = 1; // SyntaxError: Identifier 'a' has already been declared
}
try {
} catch (e) {
  let e; // SyntaxError: Identifier 'e' has already been declared
}

如果你在 REPL(例如 Firefox Web 控制檯(工具 > Web 開發者 > Web 控制檯))中進行實驗,並在兩個不同的輸入中執行兩個同名的 let 宣告,你可能會得到相同的重新宣告錯誤。有關此問題的進一步討論,請參閱 Firefox bug 1580891。Chrome 控制檯允許在不同的 REPL 輸入之間重新宣告 let

你可能會在 switch 語句中遇到錯誤,因為只有一個塊。

js
let x = 1;

switch (x) {
  case 0:
    let foo;
    break;
  case 1:
    let foo; // SyntaxError: Identifier 'foo' has already been declared
    break;
}

為了避免錯誤,請將每個 case 包裝在一個新的塊語句中。

js
let x = 1;

switch (x) {
  case 0: {
    let foo;
    break;
  }
  case 1: {
    let foo;
    break;
  }
}

示例

作用域規則

透過 let 宣告的變數在其宣告的塊以及任何包含的子塊中都具有作用域。透過這種方式,let 的工作方式與 var 非常相似。主要區別在於 var 變數的作用域是整個封閉函式。

js
function varTest() {
  var x = 1;
  {
    var x = 2; // same variable!
    console.log(x); // 2
  }
  console.log(x); // 2
}

function letTest() {
  let x = 1;
  {
    let x = 2; // different variable
    console.log(x); // 2
  }
  console.log(x); // 1
}

在程式和函式的頂層,letvar 不同,它不會在全域性物件上建立屬性。例如:

js
var x = "global";
let y = "global";
console.log(this.x); // "global"
console.log(this.y); // undefined

TDZ 與詞法作用域結合

以下程式碼在所示行會產生 ReferenceError

js
function test() {
  var foo = 33;
  if (foo) {
    let foo = foo + 55; // ReferenceError
  }
}
test();

if 塊被評估,因為外部的 var foo 有一個值。然而,由於詞法作用域,這個值在塊內部不可用:if 塊_內部_的識別符號 foolet foo。表示式 foo + 55 丟擲 ReferenceError,因為 let foo 的初始化尚未完成——它仍然在暫時性死區。

在以下情況中,這種現象可能會令人困惑。指令 let n of n.a 已經位於 for...of 迴圈塊的作用域內。因此,識別符號 n.a 被解析為指令自身第一部分(let n)中 n 物件的屬性 a。這仍然處於暫時性死區,因為其宣告語句尚未被執行和終止。

js
function go(n) {
  // n here is defined!
  console.log(n); // { a: [1, 2, 3] }

  for (let n of n.a) {
    //          ^ ReferenceError
    console.log(n);
  }
}

go({ a: [1, 2, 3] });

其他情況

當在塊內部使用時,let 將變數的作用域限制在該塊中。請注意與 var 的區別,var 的作用域在其宣告的函式內部。

js
var a = 1;
var b = 2;

{
  var a = 11; // the scope is global
  let b = 22; // the scope is inside the block

  console.log(a); // 11
  console.log(b); // 22
}

console.log(a); // 11
console.log(b); // 2

然而,下面的 varlet 宣告的組合是一個 SyntaxError,因為 var 沒有塊級作用域,導致它們在同一個作用域內。這導致了變數的隱式重新宣告。

js
let x = 1;

{
  var x = 2; // SyntaxError for re-declaration
}

使用解構進行宣告

每個 = 的左側也可以是一個繫結模式。這允許一次建立多個變數。

js
const result = /(a+)(b+)(c+)/.exec("aaabcc");
let [, a, b, c] = result;
console.log(a, b, c); // "aaa" "b" "cc"

欲瞭解更多資訊,請參閱解構

規範

規範
ECMAScript® 2026 語言規範
# sec-let-and-const-declarations

瀏覽器相容性

另見