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