W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎勵
本文用于幫助理解舊腳本
本文所講的內(nèi)容對于幫助理解舊腳本很有用。
但這不是我們編寫新代碼的方式。
在本教程最開始那部分的 變量 這章中,我們提到了變量聲明的三種方式:
let
?const
?var
?var
聲明與 let
相似。大部分情況下,我們可以用 let
代替 var
或者 var
代替 let
,都能達(dá)到預(yù)期的效果:
var message = "Hi";
alert(message); // Hi
但實(shí)際上 var
卻是一頭非常不同的,源自遠(yuǎn)古時代的怪獸。在現(xiàn)代腳本中一般不再使用它,但它仍然潛伏在舊腳本中。
如果你不打算接觸這樣的腳本,你甚至可以跳過本章或推遲閱讀本章。
另一方面,了解將舊腳本從 var
遷移到 let
時的區(qū)別,以避免奇怪的錯誤,是很重要的。
用 var
聲明的變量,不是函數(shù)作用域就是全局作用域。它們在代碼塊外也是可見的(譯注:也就是說,var
聲明的變量只有函數(shù)作用域和全局作用域,沒有塊級作用域)。
舉個例子:
if (true) {
var test = true; // 使用 "var" 而不是 "let"
}
alert(test); // true,變量在 if 結(jié)束后仍存在
由于 var
會忽略代碼塊,因此我們有了一個全局變量 test
。
如果我們在第二行使用 let test
而不是 var test
,那么該變量將僅在 if
內(nèi)部可見:
if (true) {
let test = true; // 使用 "let"
}
alert(test); // ReferenceError: test is not defined
對于循環(huán)也是這樣的,var
聲明的變量沒有塊級作用域也沒有循環(huán)局部作用域:
for (var i = 0; i < 10; i++) {
var one = 1;
// ...
}
alert(i); // 10,"i" 在循環(huán)結(jié)束后仍可見,它是一個全局變量
alert(one); // 1,"one" 在循環(huán)結(jié)束后仍可見,它是一個全局變量
如果一個代碼塊位于函數(shù)內(nèi)部,那么 var
聲明的變量的作用域?qū)楹瘮?shù)作用域:
function sayHi() {
if (true) {
var phrase = "Hello";
}
alert(phrase); // 能正常工作
}
sayHi();
alert(phrase); // ReferenceError: phrase is not defined
可以看到,var
穿透了 if
,for
和其它代碼塊。這是因?yàn)樵谠缙诘?JavaScript 中,塊沒有詞法環(huán)境,而 var
就是這個時期的代表之一。
如果我們用 let
在同一作用域下將同一個變量聲明兩次,則會出現(xiàn)錯誤:
let user;
let user; // SyntaxError: 'user' has already been declared
使用 var
,我們可以重復(fù)聲明一個變量,不管多少次都行。如果我們對一個已經(jīng)聲明的變量使用 var
,這條新的聲明語句會被忽略:
var user = "Pete";
var user = "John"; // 這個 "var" 無效(因?yàn)樽兞恳呀?jīng)聲明過了)
// ……不會觸發(fā)錯誤
alert(user); // John
當(dāng)函數(shù)開始的時候,就會處理 var
聲明(腳本啟動對應(yīng)全局變量)。
換言之,var
聲明的變量會在函數(shù)開頭被定義,與它在代碼中定義的位置無關(guān)(這里不考慮定義在嵌套函數(shù)中的情況)。
那么看一下這段代碼:
function sayHi() {
phrase = "Hello";
alert(phrase);
var phrase;
}
sayHi();
……從技術(shù)上講,它與下面這種情況是一樣的(var phrase
被上移至函數(shù)開頭):
function sayHi() {
var phrase;
phrase = "Hello";
alert(phrase);
}
sayHi();
……甚至與這種情況也一樣(記住,代碼塊是會被忽略的):
function sayHi() {
phrase = "Hello"; // (*)
if (false) {
var phrase;
}
alert(phrase);
}
sayHi();
人們將這種行為稱為“提升”(英文為 “hoisting” 或 “raising”),因?yàn)樗械?nbsp;var
都被“提升”到了函數(shù)的頂部。
所以,在上面的例子中,if (false)
分支永遠(yuǎn)都不會執(zhí)行,但沒關(guān)系,它里面的 var
在函數(shù)剛開始時就被處理了,所以在執(zhí)行 (*)
那行代碼時,變量是存在的。
聲明會被提升,但是賦值不會。
我們最好用例子來說明:
function sayHi() {
alert(phrase);
var phrase = "Hello";
}
sayHi();
var phrase = "Hello"
這行代碼包含兩個行為:
var
? 聲明變量=
? 給變量賦值。聲明在函數(shù)剛開始執(zhí)行的時候(“提升”)就被處理了,但是賦值操作始終是在它出現(xiàn)的地方才起作用。所以這段代碼實(shí)際上是這樣工作的:
function sayHi() {
var phrase; // 在函數(shù)剛開始時進(jìn)行變量聲明
alert(phrase); // undefined
phrase = "Hello"; // ……賦值 — 當(dāng)程序執(zhí)行到這一行時。
}
sayHi();
因?yàn)樗械?nbsp;var
聲明都是在函數(shù)開頭處理的,我們可以在任何地方引用它們。但是在它們被賦值之前都是 undefined。
上面兩個例子中,alert
運(yùn)行都不會報(bào)錯,因?yàn)樽兞?nbsp;phrase
是存在的。但是它還沒有被賦值,所以顯示 undefiend
。
在之前,JavaScript 中只有 var
這一種聲明變量的方式,并且這種方式聲明的變量沒有塊級作用域,程序員們就發(fā)明了一種模仿塊級作用域的方法。這種方法被稱為“立即調(diào)用函數(shù)表達(dá)式”(immediately-invoked function expressions,IIFE)。
如今,我們不應(yīng)該再使用 IIFE 了,但是你可以在舊腳本中找到它們。
IIFE 看起來像這樣:
(function() {
var message = "Hello";
alert(message); // Hello
})();
這里,創(chuàng)建了一個函數(shù)表達(dá)式并立即調(diào)用。因此,代碼立即執(zhí)行并擁有了自己的私有變量。
函數(shù)表達(dá)式被括號 (function {...})
包裹起來,因?yàn)楫?dāng) JavaScript 引擎在主代碼中遇到 "function"
時,它會把它當(dāng)成一個函數(shù)聲明的開始。但函數(shù)聲明必須有一個函數(shù)名,所以這樣的代碼會導(dǎo)致錯誤:
// 嘗試聲明并立即調(diào)用一個函數(shù)
function() { // <-- SyntaxError: Function statements require a function name
var message = "Hello";
alert(message); // Hello
}();
即使我們說:“好吧,那我們加一個名稱吧”,但它仍然不工作,因?yàn)?JavaScript 不允許立即調(diào)用函數(shù)聲明:
// 下面的括號會導(dǎo)致語法錯誤
function go() {
}(); // <-- 不能立即調(diào)用函數(shù)聲明
因此,需要使用圓括號把該函數(shù)表達(dá)式包起來,以告訴 JavaScript,這個函數(shù)是在另一個表達(dá)式的上下文中創(chuàng)建的,因此它是一個函數(shù)表達(dá)式:它不需要函數(shù)名,可以立即調(diào)用。
除了使用括號,還有其他方式可以告訴 JavaScript 在這我們指的是函數(shù)表達(dá)式:
/ 創(chuàng)建 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");
}();
在上面的所有情況中,我們都聲明了一個函數(shù)表達(dá)式并立即運(yùn)行它。請?jiān)僮⒁庖幌拢喝缃裎覀儧]有理由來編寫這樣的代碼。
var
與 let/const
有兩個主要的區(qū)別:
var
? 聲明的變量沒有塊級作用域,它們僅在當(dāng)前函數(shù)內(nèi)可見,或者全局可見(如果變量是在函數(shù)外聲明的)。var
? 變量聲明在函數(shù)開頭就會被處理(腳本啟動對應(yīng)全局變量)。涉及全局對象時,還有一個非常小的差異,我們將在下一章中介紹。
這些差異使 var
在大多數(shù)情況下都比 let
更糟糕。塊級作用域是這么好的一個東西。這就是 let
在幾年前就被寫入到標(biāo)準(zhǔn)中的原因,并且現(xiàn)在(與 const
一起)已經(jīng)成為了聲明變量的主要方式。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: