JavaScript變量聲明的var、let、const詳解
前言
一個(gè)程序語言在運(yùn)行的過程中,變量的聲明在整個(gè)程序的生命周期中,是不斷在進(jìn)行的過程。任何程序的計(jì)算都會(huì)涉及至少一個(gè)變量,而計(jì)算的結(jié)果的則可能會(huì)涉及到另外的一個(gè)或者多個(gè)變量。變量在使用前是要聲明,變量聲明的過程在計(jì)算機(jī)的底層,牽涉到的是內(nèi)存空間和內(nèi)存地址的分配。當(dāng)然啦,有內(nèi)存空間的分配,就會(huì)有內(nèi)存空間的回收和再分配。可以看出,變量聲明是我們?cè)诮佑|一門程序語言中起始的也是很關(guān)鍵的一步。
而本篇文章想跟大家聊聊的,是關(guān)于JavaScript的變量聲明中的一些內(nèi)容。
內(nèi)容
JavaScript的變量聲明
雖然一門語言的語法發(fā)展,變量聲明的語法和方式通常是比較起始的。程序語言的設(shè)計(jì)者通常都會(huì)在語言的初始的版本盡量根據(jù)自己對(duì)語言的設(shè)計(jì)思想,設(shè)計(jì)好語言的變量聲明的語法和方式,以保證好程序語言在未來的很長的道路上有一個(gè)比較好的起始。但是,JavaScript卻沒有一個(gè)這么好的起始。
JavaScript的設(shè)計(jì)之初衷,就是一門為了解決簡(jiǎn)單的網(wǎng)頁互動(dòng)的腳本語言。它的設(shè)計(jì),只用了短短的10天時(shí)間。就像初生的嬰兒一樣,稚嫩而沒有太多的規(guī)劃和愿景。設(shè)計(jì)者做夢(mèng)都沒有想到,JavaScript將來可以在整個(gè)互聯(lián)網(wǎng)的發(fā)展中占有這么大的一個(gè)分量,不僅在瀏覽器端,而且在移動(dòng)端、APP端、服務(wù)端、桌面應(yīng)用上都扮演著重要的角色。
JavaScript的設(shè)計(jì)的雛形是稚嫩的,但是隨著這么語言逐漸的受到關(guān)注和使用,語言本身的設(shè)計(jì)和語法,也在不斷的發(fā)展著。而JavaScript的變量聲明方式也從野蠻的var
的聲明方式,前行到了ECMAScript標(biāo)準(zhǔn)的let
和const
的聲明方式。
接下來,我們一起來討論它們。
var的變量聲明
var用于聲明一個(gè)函數(shù)范圍或者全局范圍的變量,并且可以為其初始化一個(gè)值。它是ECMAScript2015之前的變量聲明的唯一關(guān)鍵字,曾經(jīng)承載過一代前端開發(fā)者的青蔥歲月以及水深火熱。它具有如下的特性:
變量聲明在函數(shù)作用域中
與大多數(shù)的語言不同,作為過去的JavaScript的唯一變量聲明方式,var
的所聲明的變量并不是聲明在塊級(jí)作用域中 ,而是聲明在最近的函數(shù)上下文中,也就是局部函數(shù)作用域。對(duì)于未在任何函數(shù)中聲明的變量,其聲明范圍則是在全局對(duì)象global
之中。
/* === 全局作用域 start === */ /* 變量globalVar聲明在任何函數(shù)之外,為全局作用域中的變量 任何的函數(shù)和方法都能訪問到這個(gè)變量 */ var globalVar = 'global var'; /* === f1函數(shù)作用域 start === */ function f1() { /* 變量localVar1聲明在函數(shù)f1中,為f1的局部函數(shù)作用域中的變量 只有代碼詞法分析中的f1函數(shù)的內(nèi)部函數(shù)能夠訪問到這個(gè)變量 在這個(gè)作用域中可以訪問到全局作用域中的變量globalVar */ var localVar1 = 'local var1'; /* === f2函數(shù)作用域 start === */ return function f2() { /* 變量localVar2聲明在函數(shù)f2中,為f2的局部函數(shù)作用域中的變量, 只有代碼詞法分析中的f2函數(shù)的內(nèi)部函數(shù)能夠訪問到這個(gè)變量, 在這個(gè)作用域中可以訪問到全局作用域中變量globalVar以及外層f1函數(shù)作用域中的localVar1 */ var localVar2 = 'local var2'; return localVar2; /* === f2函數(shù)作用域 end === */ } /* === f1函數(shù)作用域 end === */ } f1(); /* === 全局作用域 end === */
上面的代碼中,globalVar
作為定義在全局對(duì)象global
中的一個(gè)全局變量,除了可以直接通過變量標(biāo)識(shí)符globalVar
訪問到之外,也可以通過global.globalVar
或者window.globalVar
的對(duì)象屬性訪問方式來訪問到。
同時(shí)還能看到,上面的作用域定義構(gòu)建成了一條global -> f1 -> f2
的函數(shù)作用域鏈, 函數(shù)執(zhí)行過程中的變量訪問會(huì)根據(jù)這條函數(shù)作用域鏈進(jìn)行規(guī)則查找和訪問。
變量重復(fù)聲明
var
的變量聲明支持在同一作用域中進(jìn)行同一個(gè)變量的多次重復(fù)的聲明,多次聲明中只有首次的變量聲明會(huì)被執(zhí)行,其他聲明會(huì)因?yàn)楫?dāng)前的執(zhí)行上下文對(duì)象中已存在當(dāng)前變量而被忽略。但是,聲明變量同時(shí)如果對(duì)變量進(jìn)行了初始賦值,賦值操作依舊會(huì)被執(zhí)行。
/* 變量的首次聲明在當(dāng)前執(zhí)行上下文對(duì)象中新增一個(gè)value變量 */ var value = 1; /* 除了變量的首次聲明外,其他相同的聲明,都不會(huì)被執(zhí)行, 但是,聲明中的初始賦值依舊會(huì)被當(dāng)作正常的賦值操作執(zhí)行 */ var value = 2; console.log(value); // 2
變量聲明提升
var
的變量聲明存在“提升”(hoisting)的特性。JavaScript在執(zhí)行函數(shù)代碼的前,首先會(huì)對(duì)函數(shù)內(nèi)當(dāng)前執(zhí)行上下文中的所有var
的變量聲明進(jìn)行掃描確認(rèn),并將所有的聲明按順序提前到當(dāng)前執(zhí)行上下文的頂部,也就是函數(shù)內(nèi)的頂部。這種變量聲明提升的特性,讓我們?cè)谕蛔饔糜蛑械目梢灾苯釉L問和使用一個(gè)在后續(xù)的代碼才進(jìn)行了首次變量聲明的變量,即在變量聲明之前使用變量。
雖然,變量的聲明在執(zhí)行時(shí)被提升了,但是,變量聲明中的初始賦值操作卻并沒有被提升,從而形成一種聲明和初始賦值的執(zhí)行邏輯上的割裂。也就是說,即使我們可以直接訪問和使用在后續(xù)的代碼才被聲明和賦值的變量,然而,變量的值卻是undefined
。代碼一直執(zhí)行到聲明和初始賦值語句后,變量的值才會(huì)被賦值為它的初始賦值。
/* 由于變量聲明提升了,可以在變量聲明前訪問使用變量, 但是,訪問到的變量值是undefined */ console.log(value); // undefined /* 變量賦值并沒有被提升,只有執(zhí)行完這一句語句,變量才被賦值為1 */ var value = 1; console.log(value); // 1
怪異危險(xiǎn)的var
var
的這些的怪異的特性,雖然在程序上都屬于合法可運(yùn)行,但是,對(duì)于編寫程序的人來說,有著許多有違正常邏輯的地方,而且容易造成代碼調(diào)試的困難。例如:面對(duì)變量的聲明和管理,我們不得不以函數(shù)為基本的管理區(qū)進(jìn)行管理;對(duì)變量的重復(fù)聲明,讓我們對(duì)代碼從上而下的變量的安全訪問和變量值的確認(rèn)變得不容易控制;
雖然,為了防止這些危險(xiǎn)的出現(xiàn),我們可以通過編程習(xí)慣的方式來進(jìn)行約束。但是,這些終究只是軟約束,隨著JavaScript的不斷發(fā)展和使用來構(gòu)建復(fù)雜的應(yīng)用,這些約束就愈加捉襟見肘。
let和const的變量聲明
黑暗和混沌,終于迎來了曙光的到來。ECMAScript2015的到來,就像一束光輝照耀進(jìn)來,給JavaScript變量聲明這一塊帶來了翻天覆地的變化。不僅帶來了let
和const
這兩個(gè)新的變量聲明關(guān)鍵字,而且還直接一躍變成了最佳的變量聲明方式。
而這些驚天動(dòng)地變化的出現(xiàn)都是因?yàn)椋鄬?duì)于var
,let
和const
帶來了如下新的共同特征:
塊級(jí)作用域
let
和const
關(guān)鍵字帶給我們的第一個(gè)讓人興奮的特性就是塊級(jí)作用域??嘤谥?code>var關(guān)鍵字的基于函數(shù)作用域的特性,我們不得在函數(shù)范圍內(nèi)小心的定義變量名稱和使用變量,特別是在條件判斷和循環(huán)邏輯中,甚至不得不在循環(huán)中使用上立即執(zhí)行函數(shù)來進(jìn)行變量值的讀取和保存。
function f() { /* 由于var基于函數(shù)作用域,回調(diào)函數(shù)內(nèi)的i變量都為同一個(gè)副本, 下面程序的輸出結(jié)果為5、5、5、5、5 */ for (var i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }); } /* 通過立即執(zhí)行函數(shù)來構(gòu)建基本數(shù)據(jù)類型的值傳遞,從而創(chuàng)建新的變量副本, 下面程序的輸出結(jié)果為0、1、2、3、4 */ for (var i = 0; i < 5; i++) { (function(i) { setTimeout(() => { console.log(i); }, 0); })(i); } } f();
現(xiàn)在,我們可以通過花括號(hào){}
以及let
和const
關(guān)鍵詞聲明限制于塊級(jí)作用域中的變量了。if
塊、while
塊、function
塊甚至單獨(dú)的{}
都是良好的塊級(jí)作用域聲明區(qū)塊。還有一點(diǎn),相對(duì)于var
,在全局作用域中使用let
和const
聲明的變量并不會(huì)添加到全局上下文對(duì)象global
中。綜合看來,塊級(jí)作用域的特性使我們的變量的聲明和使用更加的簡(jiǎn)潔和安全。
/* 由于使用的基于跨級(jí)作用域的let,每次循環(huán)體都會(huì)產(chǎn)生一個(gè)i變量的副本, 下面程序的輸出結(jié)果為0、1、2、3、4 */ for (let i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }, 0); } if (true) { let a = 1; } /* 拋出ReferenceError, 因?yàn)橛胠et定義的a變量限制于塊級(jí)作用域,只在前面方括號(hào)中可以訪問 */ console.log(a); // ReferenceError /* let和const聲明在全局作用域中聲明的變量,不會(huì)被隱晦添加到global對(duì)象中, 保證了全局環(huán)境的安全, */ let b = 1; console.log(window.b); // undefined
不可重復(fù)聲明
相比于var
的重復(fù)聲明會(huì)被忽略,let
和const
在同一作用域中對(duì)同一標(biāo)識(shí)符的變量不能重復(fù)聲明。這種變量聲明執(zhí)行上的約束,避免了同作用域下不同位置的變量聲明沖突,消除了許多不容易預(yù)見的運(yùn)行問題,讓JavaScript在構(gòu)建復(fù)雜應(yīng)用中的變量聲明過程變得更加的安全。
let a = 1; { /* 不在同一塊級(jí)作用域內(nèi),而在子級(jí)塊級(jí)作用域內(nèi) */ let a = 1; } /* 和最上面的聲明在同一塊級(jí)作用域內(nèi),屬于重復(fù)聲明, 這條語句執(zhí)行會(huì)拋出SyntaxError */ let a = 1; // SyntaxError
暫時(shí)性死區(qū)
相對(duì)于var
的變量聲明提前導(dǎo)致的變量在函數(shù)作用域中可以“先使用后聲明”的現(xiàn)象。let
和const
關(guān)鍵字聲明的變量也具有執(zhí)行中變量聲明提前的特性,但是卻不能在變量完成聲明之前進(jìn)行訪問和修改,否則會(huì)拋出ReferenceError
的錯(cuò)誤。
這種變量聲明所綁定的塊級(jí)作用域的頂部,一直到變量聲明語句執(zhí)行完成之前的,變量不能訪問和修改的區(qū)域,稱為這個(gè)變量的暫時(shí)性死區(qū)(TDZ,temporal dead zone)。暫時(shí)性死區(qū)的引入,在代碼執(zhí)行上約束了變量必須遵循“聲明后才能使用”的原則,使JavaScript的變量聲明和使用更加安全和具有更好的可讀性。
{ /* === 變量a和b的TDZ start === */ /* 拋出ReferenceError, TDZ范圍內(nèi)不能讀取和修改變量b */ console.log(b); // ReferenceError let a = 1; /* === 變量a的TDZ end === */ let b = 2; /* === 變量b的TDZ end === */ console.log(a); // 1 }
值得注意的是,暫時(shí)性死區(qū)是基于執(zhí)行順序(時(shí)間)上的,而不是編寫代碼順序(位置)上的。只要變量的訪問和修改的代碼的執(zhí)行,是在變量聲明之后,就是合法的。
{ /* === 變量value的TDZ start ===*/ function f() { console.log(value); } /* 在DZT內(nèi)訪問和修改變量value,會(huì)拋出錯(cuò)誤 */ let value = 1; /* === 變量value的TDZ end ===*/ /* 函數(shù)f中對(duì)value的訪問發(fā)生在TDZ外面, 所以訪問合法 */ f(); // 1 }
使用好let和const
let
和const
兩者的這些新的共同特性,讓它們直接變成了變量聲明的最佳方式和實(shí)踐。但是,兩者的除了上面說道的共同特性,還存在一個(gè)兩者的差異。
簡(jiǎn)單來說,let
和const
的區(qū)別在于,let
用于聲明基于塊級(jí)作用域的變量,而const
用于聲明基于塊級(jí)作用域的常量。
- 使用
let
聲明的變量,在變量的整個(gè)生命周期中,能夠?qū)ψ兞侩S時(shí)進(jìn)行賦值修改。 - 使用
const
聲明的變量為引用常量,必須在聲明的同時(shí)進(jìn)行初始賦值,在變量的整個(gè)生命周期中,無法再通過賦值的方式來修改變量值。
const
的這個(gè)無法再賦值的特性,在不同的變量類型下會(huì)有不同的表現(xiàn):
- 如果
const
聲明并且初始化賦值是基礎(chǔ)數(shù)據(jù)類型變量(String
、Number
、Boolean
、Symbol
),那么該變量之后就不能再進(jìn)行任何值的修改了。因?yàn)榛A(chǔ)數(shù)據(jù)類型變量的值必須通過變量賦值來進(jìn)行修改。 - 如果
const
聲明并且初始化賦值是引用數(shù)據(jù)類型變量(Array
、Object
、Map
、Set
),那么我們則可以隨意對(duì)該變量的字段屬性進(jìn)行修改。因?yàn)橐脭?shù)據(jù)類型變量的內(nèi)容修改(新增、修改、刪除屬性)并不會(huì)出現(xiàn)所聲明變量的賦值操作.
/* let聲明的變量可以任意賦值和修改 */ let value1 = 1; value1 = 2; /* 拋出SyntaxError, const聲明的變量必須賦值初始值 */ const value2; // SyntaxError /* 拋出TypeError, const聲明的變量在整個(gè)變量生命周期內(nèi)不能再賦值 */ const value3 = 3; value3 = 4 // TypeError /* const聲明的引用數(shù)據(jù)類型變量,依舊可以修改其字段值 */ const value4 = { key1: 1, key2: 2, }; value4.key = 3;
在實(shí)踐中,我們應(yīng)該盡可能的多使用const
進(jìn)行變量聲明,當(dāng)確定一個(gè)變量需要重新賦值的時(shí)候才將其改用let
進(jìn)行聲明,這樣可以讓我們對(duì)程序執(zhí)行中的可能發(fā)生重新賦值的變量有一個(gè)清晰的了解,減少可能出現(xiàn)的bug。
總結(jié)
ECMAScript2015帶來了let
和const
這兩個(gè)新的變量聲明關(guān)鍵字,帶來了塊級(jí)作用域和暫時(shí)性死區(qū)等新特性,解決過去var
的變量聲明中的一些怪異問題,在語法層面和運(yùn)行層面上,保證了變量聲明的“聲明后才可以使用”的安全特性,提高了JavaScript在編寫大型應(yīng)用時(shí)的變量聲明和使用安全。
當(dāng)前,let
和const
已經(jīng)是我們?nèi)粘i_發(fā)中的變量聲明的最佳時(shí)間方式。了解它們,才能用好它們。
參考資料
- 《JavaScript高級(jí)程序設(shè)計(jì)》
- 《你不知道的JavaScript》
- MDN
到此這篇關(guān)于JavaScript變量聲明的var、let、const詳解的文章就介紹到這了,更多相關(guān)JS變量聲明var、let、const內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實(shí)現(xiàn)倒計(jì)時(shí)跳轉(zhuǎn)頁面功能【實(shí)用】
本文分享了JavaScript實(shí)現(xiàn)倒計(jì)時(shí)跳轉(zhuǎn)頁面功能的具體實(shí)例代碼,頁面代碼簡(jiǎn)單,直接拷貝就能運(yùn)行,頁面可以自己美化下哦。需要的朋友一起來看下吧2016-12-12javascript判斷ie瀏覽器6/7版本加載不同樣式表的實(shí)現(xiàn)代碼
ie6/ie7的兼容問題很讓人苦惱,我們可以針對(duì)這兩個(gè)版本的瀏覽器單獨(dú)寫?yīng)毩⒌臉邮奖?,來解決兼容問題。這里的例子以判斷ie6與ie7來加載不同的樣式表2011-12-12學(xué)習(xí)Bootstrap滾動(dòng)監(jiān)聽 附調(diào)用方法
這篇文章主要為大家全面解析Bootstrap中滾動(dòng)監(jiān)聽的使用方法,感興趣的小伙伴們可以參考一下2016-07-07JavaScript基礎(chǔ)入門之錯(cuò)誤捕獲機(jī)制
初級(jí)開發(fā)人員往往很少使用js的拋出和捕獲異常,但拋出和捕獲異常往往是非常必要的,這篇文章主要給大家介紹了關(guān)于JavaScript基礎(chǔ)入門之錯(cuò)誤捕獲機(jī)制的相關(guān)資料,需要的朋友可以參考下2021-08-08javascript實(shí)現(xiàn)tab切換特效
這篇文章主要介紹了javascript實(shí)現(xiàn)tab切換特效,實(shí)現(xiàn)的方法很簡(jiǎn)單,特別適合初學(xué)者學(xué)習(xí)javascript實(shí)現(xiàn)tab切換特效,tab切換再也不是問題,需要的朋友可以參考下2015-11-11