2022 年 11 月 13 日

舊的「var」

本文旨在協助理解舊腳本

本文中的資訊有助於理解舊腳本。

我們不是這樣撰寫新程式碼的。

在關於 變數 的第一個章節中,我們提到了三種宣告變數的方式

  1. let
  2. const
  3. var

var 宣告類似於 let。在大部分情況下,我們可以用 var 取代 let,反之亦然,並且預期程式碼能正常執行

var message = "Hi";
alert(message); // Hi

但內部而言,var 是一個非常不同的野獸,它起源於非常久遠的時代。它通常不會用於現代腳本,但仍然潛伏在舊腳本中。

如果您不打算遇到此類指令碼,您甚至可以跳過此章節或將其推遲。

另一方面,在將舊指令碼從 var 遷移到 let 時了解差異非常重要,以避免出現奇怪的錯誤。

“var” 沒有區塊範圍

使用 var 宣告的變數是函式範圍或全域範圍。它們在區塊中可見。

例如

if (true) {
  var test = true; // use "var" instead of "let"
}

alert(test); // true, the variable lives after if

由於 var 忽略程式碼區塊,因此我們獲得全域變數 test

如果我們使用 let test 而不是 var test,則變數只會在 if 內部可見

if (true) {
  let test = true; // use "let"
}

alert(test); // ReferenceError: test is not defined

迴圈也一樣:var 不能是區塊或迴圈區域

for (var i = 0; i < 10; i++) {
  var one = 1;
  // ...
}

alert(i);   // 10, "i" is visible after loop, it's a global variable
alert(one); // 1, "one" is visible after loop, it's a global variable

如果程式碼區塊在函式內,則 var 會變成函式層級變數

function sayHi() {
  if (true) {
    var phrase = "Hello";
  }

  alert(phrase); // works
}

sayHi();
alert(phrase); // ReferenceError: phrase is not defined

正如我們所見,var 穿透 iffor 或其他程式碼區塊。這是因為在 JavaScript 中的很久以前,區塊沒有詞彙環境,而 var 是它的殘餘。

“var” 容忍重新宣告

如果我們在同一個範圍內使用 let 兩次宣告同一個變數,這是一個錯誤

let user;
let user; // SyntaxError: 'user' has already been declared

使用 var,我們可以重新宣告一個變數任意次數。如果我們對已宣告的變數使用 var,它只會被忽略

var user = "Pete";

var user = "John"; // this "var" does nothing (already declared)
// ...it doesn't trigger an error

alert(user); // John

“var” 變數可以在其使用位置下方宣告

當函式開始(或全域變數的指令碼開始)時,會處理 var 宣告。

換句話說,var 變數從函式開始就已定義,無論定義在哪裡(假設定義不在巢狀函式中)。

所以這個程式碼

function sayHi() {
  phrase = "Hello";

  alert(phrase);

  var phrase;
}
sayHi();

…在技術上與這個相同(將 var phrase 移到上方)

function sayHi() {
  var phrase;

  phrase = "Hello";

  alert(phrase);
}
sayHi();

…甚至與這個相同(請記住,程式碼區塊會被忽略)

function sayHi() {
  phrase = "Hello"; // (*)

  if (false) {
    var phrase;
  }

  alert(phrase);
}
sayHi();

人們也稱這種行為為“提升”(raising),因為所有 var 都會“提升”到函式的頂端。

因此,在上面的範例中,if (false) 分支永遠不會執行,但這並不重要。它內部的 var 在函式開始時會被處理,因此在 (*) 的時刻,變數存在。

宣告會提升,但賦值不會。

這最好用範例說明

function sayHi() {
  alert(phrase);

  var phrase = "Hello";
}

sayHi();

程式碼行 var phrase = "Hello" 其中包含兩個動作

  1. 變數宣告 var
  2. 變數賦值 =

宣告會在函式執行開始時處理(「提升」),但指定永遠在出現的地方執行。因此,程式碼基本上會像這樣執行

function sayHi() {
  var phrase; // declaration works at the start...

  alert(phrase); // undefined

  phrase = "Hello"; // ...assignment - when the execution reaches it.
}

sayHi();

由於所有 var 宣告都會在函式開始時處理,我們可以在任何地方參照它們。但變數在指定之前都是未定義的。

在以上兩個範例中,alert 都會在沒有錯誤的情況下執行,因為變數 phrase 存在。但它的值尚未指定,因此會顯示 undefined

IIFE

過去,由於只有 var,而且它沒有區塊層級可見性,因此程式設計師發明了一種模擬它的方法。他們所做的稱為「立即呼叫函式運算式」(簡稱 IIFE)。

這不是我們現在應該使用的東西,但你可以在舊腳本中找到它們。

IIFE 如下所示

(function() {

  var message = "Hello";

  alert(message); // Hello

})();

在此,會建立一個函式運算式並立即呼叫它。因此,程式碼會立即執行,並有自己的私有變數。

函式運算式用括號 (function {...}) 包起來,因為當 JavaScript 引擎在主程式碼中遇到 "function" 時,它會將其理解為函式宣告的開始。但函式宣告必須有名稱,因此這種程式碼會產生錯誤

// Tries to declare and immediately call a function
function() { // <-- SyntaxError: Function statements require a function name

  var message = "Hello";

  alert(message); // Hello

}();

即使我們說:「好吧,讓我們加上一個名稱」,這也不會起作用,因為 JavaScript 不允許函式宣告立即呼叫

// syntax error because of parentheses below
function go() {

}(); // <-- can't call Function Declaration immediately

因此,函式周圍的括號是一種技巧,用來向 JavaScript 顯示函式是在另一個運算式的內容中建立的,因此它是一個函式運算式:它不需要名稱,並且可以立即呼叫。

除了括號之外,還有其他方法可以告訴 JavaScript 我們的意思是函式運算式

// Ways to create IIFE

(function() {
  alert("Parentheses around the function");
})();

(function() {
  alert("Parentheses around the whole thing");
}());

!function() {
  alert("Bitwise NOT operator starts the expression");
}();

+function() {
  alert("Unary plus starts the expression");
}();

在以上所有情況中,我們都宣告了一個函式運算式並立即執行它。讓我們再次注意:現在沒有理由撰寫這樣的程式碼。

摘要

varlet/const 相比,有兩個主要差異

  1. var 變數沒有區塊範圍,它們的可見性範圍為目前函式,或如果在函式外部宣告,則為全域。
  2. var 宣告會在函式開始時處理(全域變數的腳本開始時)。

還有一個與全域物件相關的非常小的差異,我們將在下一個章節中介紹。

這些差異使得 var 在大多數情況下都比 let 更糟。區塊層級變數是一件很棒的事情。這就是為什麼 let 很久以前就在標準中引入,現在是宣告變數的主要方式(與 const 一起)。

教學地圖

留言

在留言之前先閱讀這段…
  • 如果你有改善建議 - 請 提交 GitHub 問題 或提交 pull request,而不是留言。
  • 如果你無法理解文章中的某些內容 – 請詳細說明。
  • 若要插入幾行程式碼,請使用 <code> 標籤,對於多行 – 請將它們包在 <pre> 標籤中,對於超過 10 行 – 請使用沙盒(plnkrjsbincodepen…)