引言
???????在學習javascript的過程中,變量是無時無刻不在使用的。那么相對應的,變量聲明方法也如是。變量是由自己決定,但變量聲明方法是早已經定義好的。那么在使用變量之前,了解變量聲明方法,就變得尤為重要。在ES6推出之前,最常用的聲明變量方法就是var。但是由于var自身的缺陷,ES6推出了let和const替代var。雖然修正了var的缺陷,但不能改變的,是之前已經用var寫了多少年的項目,這些項目是無法隨著var的被取代而輕易更改的。所以仍存在著使用var的公司和項目,這也使得了解var、let、const的區別變得有必要。
一、var
???????在說明var并看代碼之前,我們先統一思路,將變量的聲明及使用過程分為:創建→初始化→賦值→修改四步。
首先,來看var的聲明及使用:
function test() {
/*
預解析,初始化與賦值分離
var a = undefined // 創建變量a,并初始化為undefined。此時變量已經存在與環境中
*/
console.log(a) //undefined
var a = 0; //賦值為0
console.log(a); // 0
a = 1; //可以修改變量值
console.log(a) // 1
}
test();
以上代碼可以看出,var在它的執行環境中的聲明及操作過程為:
( 創建→初始化 )→賦值→修改
- 預解析:在環境最頂端,創建變量,并初始化為undefined—— 變量提升;
- 為變量賦值;
- 對變量進行操作,可以在后續操作中對變量值進行修改;
???????通過console.log的打印結果,我們可以清晰的認識到一點——var的初始化與賦值是分離的,而且初始化的過程優先于執行環境中的所有操作。這就是為什么在var聲明賦值前console.log變量,會打印出undefined,而不是報錯的原因。
???????var聲明的初始化先于賦值的現象,就叫做變量提升。
然后著重說一下變量提升
- if判斷中的變量提升
function test() {
/*
預解析
雖然if判斷沒有執行,但var的變量提升已經發生,此時執行環境中,已經存在變量a,值為undefined;
*/
console.log(a) // undefined,變量提升
if (false) {
var a = 1;
console.log(a) //不執行
}
console.log(a); //undefined,變量未賦值
a = 1; //注意,此時因為a的變量提升,未加聲明符號的賦值,并沒有提升到全局環境中
console.log(a); // 1
}
test();
console.log(a); // 報錯,因為if中a的變量提升,a = 1的賦值并沒有存在與全局中。如果注釋掉if判斷中的內容,a = 1因為沒加變量聲明符號,相當于在與全局中聲明,那么最后的console.log(a)將打印1
2.for循環中的變量提升
function test() {
/*
預解析
雖然if判斷沒有執行,但var的變量提升已經發生,此時執行環境中,已經存在變量a,值為undefined;
*/
console.log(a) // undefined,變量提升
console.log(i); //undefined,變量提升
for (var i = 0;i < 0;i++) {
var a = 1;
console.log(a) //未執行
}
console.log(i); //1,for中的初始化語句已經執行
console.log(a); //undefined,變量未賦值
a = 1;
console.log(a);
}
test();
console.log(a); //報錯,變量不存在
???????從上邊代碼中可以看出,當var聲明存在于if和for循環中時,不管賦值有沒有執行,創建及初始化的過程都已經提升到了執行環境中。
最后說明var的一個缺陷:
function test() {
var a = 0;
var a = 1;
var a = 2;
console.log(a); // 2
}
test();
???????由以上代碼可以看出,var聲明一個變量后,可以無限次的以同一個變量名不斷的重復 創建→初始化→賦值,這跟直接修改變量值的結果是一樣的。但是實際操作中不會有人通過這種方式操作變量,而且如果項目很大,很難保證不出現給不同變量聲明同一個變量名的情況,很容易出現錯誤。
二、let
首先,來看var的聲明及使用:
function test() {
/*
預解析,沒有變量提升,啥也沒有。創建、初始化與賦值同時進行
*/
console.log(a); //報錯,變量仍未創建
let a = 0; // 創建變量a,初始化并賦值為0.不賦值的話,則為undefined;
console.log(a); // 0
a = 2; // 可以修改變量值
console.log(a); // 2
}
test();
同樣,先分析過程:
( 創建→初始化→賦值 )→修改
- 預解析,啥也沒有;
在環境最頂端,創建變量,并初始化為undefined—— 變量提升; - 創建變量、初始化并賦值;
- 對變量進行操作,可以在后續操作中對變量值進行修改;
???????通過上方代碼,我們可以看出let聲明變量時,( 創建→初始化→賦值 )是在一步完成的,不存在變量提升的現象。所以在let聲明前console.log(a),報錯a is not defined,因為此時a還沒有被創建。而let初始化前的執行區域就叫做暫存死區。
然后,來看let在重復聲明時的表現:
function test() {
let a = 1;
let a = 2;
let a = 3;
console.log(a); // 報錯
}
test();
???????以上說明了let區別于var的另一個特性——變量的唯一性。同一個變量名,不能在let中重復使用,所以執行上方代碼操作的結果,就是報錯Identifier 'a' has already been declared;
最后,let與var最大的區別——塊級作用域
???????首先說明,塊級作用域的概念——簡單理解,{}一個大括號就是一個代碼塊,一個單獨的執行環境。那么它理應不受外部影響(如果不是刻意為之的話,它也不應該影響外部環境)。
???????但是在let之前,JS中的變量聲明是沒有塊級作用域的屬性的。這其中最典型的案例,就是for循環中的var聲明i。
function a() {
for (var i = 1;i < 5;i++) {
console.log(i);
for( var i = 10;i < 20;i++) {
console.log(i);
}
}
}
a(); //只執行外部循環一次,因為內部循環由于var聲明的緣故,執行過后i值已經為11,外部循環判斷后,將不再執行
function testA() {
/*
預解析
創建i并賦值為undefined
注意,此時實際上是兩個i的變量提升
*/
console.log(i); // undefined
var i = 0; // 賦值i = 0
console.log(i); // 0;
{
console.log(i); // 0,訪問第一個i
var i = 10 // 賦值i = 10,覆蓋第一個i
console.log(i); // 10,訪問第二個i
}
console.log(i); // 10,訪問第二個i,i已經被覆蓋
}
testA();
???????以上代碼,通過testA函數,側面解釋了一下a函數中的行為原因。這一行為模式,充分暴露了var聲明的缺陷。
???????然后再來看let聲明中的塊級作用域:
function b() {
for (let i = 0;i < 5;i++) {
console.log(i);
for(let i = 10;i < 20;i++) {
console.log(i);
}
}
}
b(); //因為塊級作用域的存在,兩個循環的i互不影響,所有循環次數都會被執行。實際使用時,建議還是不要都用同一變量名
function testB() {
/*預解析啥也沒有 */
console.log(i); // 報錯,暫存死區
let i = 0;
// 賦值i = 0
console.log(i); // 0;
{
console.log(i); // 報錯,暫存死區,因為塊中又聲明了變量i。如果塊中沒有let i的話,則按作用域鏈向上查找,打印外部i值0
let i = 10 // 賦值i = 10,不覆蓋第一個i
console.log(i); // 10,訪問第二個i
}
console.log(i); // 0,訪問第一個i,兩個i相互不影響
}
testB();
???????以上代碼,通過testB函數,側面解釋了一下b函數中的行為原因。這一行為模式,體現了let生命中塊級作用域的存在,并暴露出了let與var的最大區別。
三、const
function test() {
/*
預解析啥也沒有,創建、初始化與賦值同時進行
*/
console.log(a); // 報錯,變量仍未創建
const a = {
name: "Lyu" // 創建對象a,并賦值為一個對象地址
}
console.log(a); //{name: "Lyu"}
console.log(a.name); // Lyu
a = 1; //報錯,常量值不可修改
a.name = "Jack";
console.log(a); // {name: "Jack"}
console.log(a.name); // Jack
}
test();
分析過程:
( 創建→初始化→賦值 )→修改
- 預解析,啥也沒有;
預解析在環境最頂端,創建變量,并初始化為undefined—— 變量提升; - 創建變量、初始化并賦值。必須賦值,不賦值會報錯;
- 對變量進行操作,可以在后續操作中對變量值進行修改,不可以對變量進行修改,但是可以對變量的屬性進行修改;
???????為了說明const的特性,特意聲明了一個對象。在理解了var和let的過程之后,再來看const的整個過程,會發現在( 創建→初始化→賦值 )的過程中,const和let是沒有區別的。唯一的區別在于→修改。如果執行了上方的代碼,在a = 1那步會報錯Assignment to constant variable。其中的constant就是const的英文全拼,它的意思的不變的、恒定的、恒量。那么從字面上就能理解,通過const聲明的變量,是一個恒定值,即無法更改的值。所以當通過a = 1試圖修改a的值時,報錯。
???????雖然a本身的值無法修改,但是a為對象,值為地址,所以a的屬性是可以修改的。從代碼最后一步可以看出。a.name的值成功修改為"Jack"。這就是const 的第二個特性。
???????同let一樣的,const的第三個特性也是變量的唯一性,不再過多闡述。
四、function
首先,來看function的聲明及使用:
/*
funtcion test() {...}
預解析,創建、初始化、賦值三位一體
此時函數已經完成變量聲明的所有操作,在執行環境的任何位置都可以調用函數
*/
test(); //調用函數,只要函數確實存在并可用,那么在執行環境任何位置都可以調用
function test() {...}
分析過程:
( 創建→初始化→賦值 )→執行/修改
- 預解析,在環境最頂端,創建函數,初始化并賦值為函數定義;
- 執行函數,無論函數在何位置,只要可用,就可以調用;
???????function是專門用于函數聲明的方法,由于函數的復雜性,以及利用性。function聲明的函數,會在整個環境變量最頂端完成創建、初始化、賦值三位一體的操作。這樣一來,不管在何處聲明了函數,可以在任何地方調用函數方法。這是比較合乎常理的性質。
然后,function同var一樣,同樣存在變量聲明的特性:
- if判斷中的變量提升
function test() {
/*
預解析
雖然if判斷沒有執行,但function的變量提升已經發生,此時執行環境中,已經存在變量a,值為undefined;
*/
console.log(a) // undefined,變量提升
if (false) {
function a(){}; // 變量提升
console.log(a) //不執行
}
console.log(a); //undefined,變量未賦值
a = 1; //注意,此時因為a的變量提升,未加聲明符號的賦值,并沒有提升到全局環境中
console.log(a); // 1
}
test();
console.log(a); // 報錯,因為if中a的變量提升,a = 1的賦值并沒有存在與全局中。如果注釋掉if判斷中的內容,a = 1因為沒加變量聲明符號,相當于在與全局中聲明,那么最后的console.log(a)將打印1
- for循環中的變量提升
function test() {
/*
預解析
雖然if判斷沒有執行,但function的變量提升已經發生,此時執行環境中,已經存在變量a,值為undefined;
*/
console.log(a) // undefined,變量提升
console.log(i); //報錯,暫存死區
for (let i = 0;i < 0;i++) {
function a() {}; //變量提升
console.log(a) //未執行
}
console.log(i); //報錯,let不存在變量提升
console.log(a); //undefined,變量未賦值
a = 1;
console.log(a);
}
test();
console.log(a); //報錯,變量不存在
最后,function同var一樣,它也可以對同一變量重復聲明,而且后邊的函數定義會覆蓋前邊的函數定義:
test(); // 2
function test() {
console.log(1);
}
function test() {
console.log(2);
}
???????結果打印2,說明前邊聲明的函數方法被后邊所覆蓋。
五、總結
綜上所述,可以總結為如下幾點:
1.var與let、const的區別在于變量提升,以及變量的唯一性;
2.const與let的區別,除了變量值不能修改,其他性質一樣;
3.function由于其自身的需要,創建→初始化→賦值三位一體,在環境最頂端完成;也正因為這種性質,函數聲明的函數可以在任何位置被調用;
4.如果可以,盡量使用let、const代替var;
???????本文僅僅簡單羅列了在函數聲明及操作方面,var、let、const、function的區別,意在為初學者理清概念偏差,少走彎路,并通過理解學習,早日跨入JS門檻。
以上,如有錯誤或是紕漏,歡迎批評指正~