Variables

變數是資料結構,可為值指派代表名稱。可包含任何類型的資料。

變數名稱稱為識別碼。有效的 ID 必須遵循下列規則:

  • 識別碼可包含萬國碼字母、美元符號 ($)、底線字元 (_)、數字 (0-9),甚至是部分萬國碼字元。
  • 識別碼不得包含空白字元,因為剖析器會使用空白字元分隔輸入元素。舉例來說,如果您嘗試呼叫變數 my Variable 而非 myVariable,剖析器會看到兩個 ID (myVariable),並擲回語法錯誤 (「unexpected token: identifier」)。
  • ID 開頭必須是英文字母、底線 (_) 或美元符號 ($),且不得以數字開頭,以免與數字混淆:

    let 1a = true;
    
    > Uncaught SyntaxError: Invalid or unexpected token
    

    如果 JavaScript 允許在識別碼開頭使用數字,那麼識別碼就會只由數字組成,導致用作數字的數字與用作識別碼的數字之間發生衝突:

    let 10 = 20
    
    10 + 5
    > ?
    
  • 已在語法上具有意義的「保留字」無法用做 ID。

  • ID 不得包含特殊字元 (! . , / \ + - * =)。

以下並非建立 ID 的嚴格規則,而是業界最佳做法,可讓您更輕鬆地維護程式碼。如果您的專案有不同的標準,請改為遵循這些標準,以確保一致性。

根據 JavaScript 內建方法和屬性設定的範例,駝峰式大小寫 (也稱為「駝峰式大小寫」) 是組成多個字詞的識別子非常常見的慣例。駝峰式大小寫是指將每個字詞的第一個字母大寫,除了第一個字詞,以便在不使用空格的情況下提高可讀性。

let camelCasedIdentifier = true;

部分專案會根據資料的背景和性質採用其他命名慣例。舉例來說,類別的首字母通常會大寫,因此多字詞類別名稱通常會使用駝峰式變體,也就是通常稱為「大駝峰式」或Pascal 命名法的變體。

class MyClass {

}

識別碼應簡明地描述所含資料的性質 (例如 currentMonthDaystheNumberOfDaysInTheCurrentMonth 更適合作為名稱),並讓使用者一目瞭然 (originalValueval 更適合)。在本模組中使用的 myVariable 識別碼適用於個別範例的情況,但在實際運作程式碼中卻毫無幫助,因為它們不會提供所含資料的相關資訊。

別名不應過於明確指出所含資料,因為別名的值可能會因指令碼對該資料的操作方式,或未來維護者做出的決定而變更。舉例來說,原本具有 miles 識別碼的變數,可能需要在專案後續變更為公里值,因此維護者必須變更任何對該變數的參照,以免日後造成混淆。如要避免這種情況,請改用 distance 做為 ID。

JavaScript 不會為以底線字元 (_) 開頭的 ID 提供任何特殊權限或意義,但這些 ID 通常用於表示變數、方法或屬性為「私有」,也就是說,這些 ID 只能在包含該 ID 的物件情境中使用,且不得在該情境之外存取或修改。這是從其他程式設計語言沿用下來的慣例,早於 JavaScript 私人屬性的新增。

變數宣告

您可以透過多種方式讓 JavaScript 瞭解識別碼,這個程序稱為「宣告」變數。變數會使用 letconstvar 關鍵字宣告。

let myVariable;

使用 letvar 宣告可隨時變更的變數。這些關鍵字會告訴 JavaScript 解譯器,字串字元是可能包含值的 ID。

在現代化程式碼庫中作業時,請使用 let 而非 varvar 仍可在現代瀏覽器中運作,但它具有一些不直觀的行為,這些行為是在 JavaScript 最早期的版本中定義,且無法在日後變更以保留回溯相容性。let 已在 ES6 中新增,以解決 var 設計的一些問題。

宣告的變數會透過為變數指派值初始化。使用單一等號 (=) 為變數指派或重新指派值。您可以將此做為宣告該變數的陳述式一部分:

let myVariable = 5;

myVariable + myVariable
> 10

您也可以使用 let (或 var) 宣告變數,但不立即初始化。如此一來,變數的初始值就是 undefined,直到程式碼指派值為止。

let myVariable;

myVariable;
> undefined

myVariable = 5;

myVariable + myVariable
> 10

具有 undefined 值的變數與未定義的變數不同,後者尚未宣告 ID。參照未宣告的變數會導致錯誤。

myVariable
> Uncaught ReferenceError: myVariable is not defined

let myVariable;

myVariable
> undefined

將 ID 與值建立關聯通常稱為「繫結」。letvarconst 關鍵字後面的語法稱為「繫結清單」,可允許多個以半形逗號分隔的變數宣告 (結尾為預期的分號)。這會使下列程式碼片段在功能上相同:

let firstVariable,
     secondVariable,
     thirdVariable;
let firstVariable;
let secondVariable;
let thirdVariable;

重新指派變數值時不會使用 let (或 var),因為 JavaScript 已知變數存在:

let myVariable = true;

myVariable
> true

myVariable = false;

myVariable
> false

您可以根據變數的現有值,重新指派變數的新值:

let myVariable = 10;

myVariable
> 10

myVariable = myVariable * myVariable;

myVariable
> 100

如果您嘗試在實際工作環境中使用 let 重新宣告變數,就會發生語法錯誤:

let myVariable = true;
let myVariable = false;
> Uncaught SyntaxError: redeclaration of let myVariable

瀏覽器的開發人員工具let (和 class) 重新宣告的容許度較高,因此您可能不會在開發人員控制台中看到相同的錯誤。

為維持舊版瀏覽器的相容性,var 允許在任何情境下不帶錯誤地重新宣告不必要的項目:

var myVariable = true;
var myVariable = false;

myVariable\
> false

const

使用 const 關鍵字宣告常數,這是必須立即初始化,且之後無法變更的變數類型。常數的 ID 會遵循所有與使用 let (和 var) 宣告的變數相同的規則:

const myConstant = true;

myConstant
> true

您無法宣告常數,而不立即指派值,因為常數在建立後就無法重新指派,因此任何未初始化的常數都會永遠保持 undefined。如果您嘗試宣告常數,但未先初始化,就會收到語法錯誤:

const myConstant;
Uncaught SyntaxError: missing = in const declaration

嘗試以 let (或 var) 宣告的變數值變更方式,變更以 const 宣告的變數值,會導致類型錯誤:

const myConstant = true;

myConstant = false;
> Uncaught TypeError: invalid assignment to const 'myConstant'

不過,當常數與物件建立關聯時,該物件的屬性「可以」變更。

const constantObject = { "firstvalue" : true };

constantObject
> Object { firstvalue: true }

constantObject.secondvalue = false;

constantObject
> Object { firstvalue: true, secondvalue: false }

包含物件的常數是不可變動的對可變資料值的參照。雖然常數本身無法變更,但您可以變更、新增或移除參照物件的屬性:

const constantObject = { "firstvalue" : true };

constantObject = false
> Uncaught TypeError: invalid assignment to const 'constantObject'

如果您不預期變數會重新指派,最佳做法是將其設為常數。使用 const 可讓開發團隊或未來專案維護人員知道不要變更該值,以免違反程式碼對其使用方式所做的假設,例如變數最終會根據預期資料類型進行評估。

變數範圍

變數的範圍是指可使用該變數的指令碼部分。在變數範圍之外,系統不會將其定義為包含 undefined 值的 ID,而是視為未宣告的變數。

視您用來宣告變數的關鍵字和定義變數的上下文而定,您可以將變數的範圍設為區塊陳述式 (區塊範圍)、個別函式 (函式範圍) 或整個 JavaScript 應用程式 (全域範圍)。

區塊範圍

您使用 letconst 宣告的任何變數,其範圍都會是最近的包含區塊陳述式,也就是說,變數只能在該區塊內存取。嘗試在包含區塊外存取區塊範圍變數,會導致與嘗試存取不存在的變數相同的錯誤:

{
    let scopedVariable = true;
    console.log( scopedVariable );
}
> true

scopedVariable
> ReferenceError: scopedVariable is not defined

就 JavaScript 而言,區塊範圍變數「不會」存在於包含該變數的區塊之外。舉例來說,您可以在區塊內宣告常數,然後在該區塊外宣告另一個使用相同 ID 的常數:

{
  const myConstant = false;
}
const myConstant = true;

scopedConstant;
> true

雖然已宣告的變數無法擴展至父項區塊,但所有子項區塊都可以使用該變數:

{
    let scopedVariable = true;
    {
    console.log( scopedVariable );
    }
}
> true

您可以在子區塊中變更已宣告的變數值:

{
    let scopedVariable = false;
    {
    scopedVariable = true;
    }
    console.log( scopedVariable );
}
> true

即使新變數使用與父區塊中變數相同的 ID,也可以在子區塊內使用 letconst 進行初始化,不會發生錯誤:

{
    let scopedVariable = false;
    {
    let scopedVariable = true;
    }
    console.log( scopedVariable );
}
> false

函式範圍

使用 var 宣告的變數會在最接近的包含函式 (或 class 內的靜態初始化區塊) 中定義範圍。

function myFunction() {
    var scopedVariable = true;

    return scopedVariable;
}

scopedVariable;
> ReferenceError: scopedVariable is not defined

即使呼叫函式後,情況仍是如此。即使變數在函式執行時已初始化,在函式範圍以外仍無法使用該變數:

function myFunction() {
    var scopedVariable = true;

    return scopedVariable;
}

scopedVariable;
> ReferenceError: scopedVariable is not defined

myFunction();
> true

scopedVariable;
> ReferenceError: scopedVariable is not defined

全域範圍

全域變數可在整個 JavaScript 應用程式中使用,包括所有區塊和函式,以及網頁上的任何指令碼。

雖然這似乎是理想的預設值,但應用程式任何部分都能存取及修改的變數,可能會增加不必要的額外負擔,甚至會與應用程式中其他具有相同 ID 的變數發生衝突。這項規定適用於網頁算繪作業中涉及的所有 JavaScript,包括第三方程式庫和使用者分析等。因此,建議您盡可能避免污染全域範圍

在父函式外部使用 var 宣告的任何變數,或在父區塊外部使用 letconst 的變數,都是全域變數:

var functionGlobal = true; // Global
let blockGlobal = true; // Global

{
    console.log( blockGlobal );
    console.log( functionGlobal );
}
> true
> true

(function() {
    console.log( blockGlobal );
    console.log( functionGlobal );
}());
> true
> true

如未明確宣告變數,就為其指派值 (也就是從未使用 varletconst 建立變數),變數就會提升至全域範圍,即使在函式或區塊內初始化也一樣。使用這個模式建立的變數有時稱為「隱含的全球變數」。

function myFunction() {
    globalVariable = "global";

    return globalVariable
}

myFunction()\
> "global"

globalVariable\
> "global"

變數提升

變數和函式宣告會提升至其範圍的頂端,也就是說,JavaScript 轉譯器會處理指令碼中任何位置宣告的變數,並在執行指令碼前,將該變數有效地移至其包函範圍的第一行。也就是說,使用 var 宣告的變數可以在變數宣告前參照,而不會發生錯誤:

hoistedVariable
> undefined

var hoistedVariable;

由於只會代管變數宣告,而非初始化,因此未以 varletconst 明確宣告的變數不會提升:

unhoistedVariable;
> Uncaught ReferenceError: unhoistedVariable is not defined

unhoistedVariable = true;

如先前所述,已宣告但未初始化的變數會指派 undefined 值。這項行為也適用於提升變數宣告,但僅適用於使用 var 宣告的變數。

hoistedVariable
> undefined

var hoistedVariable = 2 + 2;

hoistedVariable\
> 4

這種不直觀的行為,主要是因為 JavaScript 早期版本的設計決策,如果要變更,就可能會導致現有網站發生問題。

letconst 會在變數建立前存取變數時擲回錯誤,以便解決這項行為:

{
    hoistedVariable;

    let hoistedVariable;
}
> Uncaught ReferenceError: can't access lexical declaration 'hoistedVariable' before initialization

這項錯誤與嘗試存取未宣告的變數時可能會發生的「hoistedVariable is not defined」錯誤不同。由於 JavaScript 已提升變數,因此會知道變數會在指定範圍內建立。不過,如果在宣告前未將該變數設為 undefined,轉譯器就會擲回錯誤。以 letconst (或 class) 宣告的變數,會從包函區塊的開頭到宣告變數的程式碼點,存在於「時間死區」(「TDZ」) 中。

時間死區可讓 let 的行為對作者來說更符合直覺,比 var 更容易理解。這對 const 的設計也至關重要。由於常數無法變更,因此將常數提升至其範圍頂端,並指定 undefined 的隱含值,就無法以有意義的值初始化。

進行隨堂測驗

您可以使用哪些字元開頭來建立 ID?

一封信
一個位數
底線

如要宣告可隨時變更值的變數,哪一種方法較佳?

const
var
let