本文中的資訊有助於理解舊腳本。
我們不是這樣撰寫新程式碼的。
在關於 變數 的第一個章節中,我們提到了三種宣告變數的方式
let
const
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
穿透 if
、for
或其他程式碼區塊。這是因為在 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"
其中包含兩個動作
- 變數宣告
var
- 變數賦值
=
。
宣告會在函式執行開始時處理(「提升」),但指定永遠在出現的地方執行。因此,程式碼基本上會像這樣執行
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");
}();
在以上所有情況中,我們都宣告了一個函式運算式並立即執行它。讓我們再次注意:現在沒有理由撰寫這樣的程式碼。
摘要
var
與 let/const
相比,有兩個主要差異
var
變數沒有區塊範圍,它們的可見性範圍為目前函式,或如果在函式外部宣告,則為全域。var
宣告會在函式開始時處理(全域變數的腳本開始時)。
還有一個與全域物件相關的非常小的差異,我們將在下一個章節中介紹。
這些差異使得 var
在大多數情況下都比 let
更糟。區塊層級變數是一件很棒的事情。這就是為什麼 let
很久以前就在標準中引入,現在是宣告變數的主要方式(與 const
一起)。
留言
<code>
標籤,對於多行 – 請將它們包在<pre>
標籤中,對於超過 10 行 – 請使用沙盒(plnkr、jsbin、codepen…)