- JS全書:JavaScript Web前端開發指南
- 高鵬
- 2595字
- 2020-09-18 10:29:15
3.2 變量和變量作用域
3.2.1 變量
變量用來存儲值或表達式,是存儲數據的容器,示例如下。
a = 1; a = a + 1; // -> 2
變量名稱的命名規則與標識符類似。
3.2.2 聲明變量
在JavaScript中,有多種聲明變量的方式。
- var
- let
- const
1. var
var聲明一個變量,可以在聲明變量的時候為其賦值,示例如下。
var a = 1;
上面的代碼很好理解,定義了一個名稱為a,值為1的變量,我們也可以先聲明變量,在必要的時候再對其進行賦值操作,示例如下。
var a; //聲明變量a a = 1; //把數字1賦值給變量a
聲明多個變量的方式如下。
var a; var b = 1;
聲明多個變量時,也可以只使用一次var關鍵字,簡寫成:
var a , b = 1;
2. 變量的作用域
ECMAScript使用的是詞法作用域(Lexical scoping,又稱“靜態作用域”),其變量又稱為“詞法變量”,詞法變量在變量聲明時確定其有效范圍,這一有效范圍就是變量的作用域(scope),在作用域外,該變量不可見。
通常,作用域是一個函數,示例如下。

ES6增加了塊級作用域:

在函數作用域和塊級作用域內聲明的變量叫作“局部變量”,除此之外的變量叫作“全局變量”。
局部變量只能在該函數或塊級作用域內被訪問;全局變量可以在當前文檔內的任何位置訪問。
全局變量其實是global對象(瀏覽器環境下,global對象指的是window對象)的屬性,可以通過window['變量名]訪問。
· 作用域鏈
當代碼在一個執行環境中執行時,會創建變量對象(又稱“活動對象”,activation object,包含形參、函數聲明、變量聲明)的一個作用域鏈,作用域鏈的最前端,始終都是當前執行的代碼所在執行環境的變量對象(如果執行環境是函數,變量對象以arguments初始化),作用域鏈的下一個對象來自包含(外部)的執行環境,再下一個則來自下一個包含執行環境,這樣一直追溯到全局執行環境global。
· 執行環境
執行環境又稱“執行上下文”,JavaScript在執行代碼時,會創建一個執行環境,該執行環境會成為當前的執行環境,每個執行環境包含3部分:
- 詞法環境,即作用域鏈。
- 變量環境,即聲明的變量。
- this綁定。
也就是說,在執行代碼時,其執行環境就已經對詞法環境、變量環境和this進行了初始化操作。
· 聲明提前(var hoisting)
我們先看以下一段代碼。
console.log(a); // > undefined console.log(b); // > Uncaught ReferenceError: b is not defined var a = 1; console.log(a); // > 1
查看變量的生命周期,變量的生命周期可以理解為3部分。
① 聲明階段,為變量創建存儲空間。
② 初始化階段,變量值被初始化為undefined。
③ 賦值階段,執行賦值操作。
聲明提前是指在進入變量的作用域時,立即完成變量的聲明階段和初始化階段。
因此,上述代碼可以看成:

其中,聲明階段就位于執行環境創建時,因此,在尚未執行代碼前,聲明的變量其值均為undefined。
3. let與const
ES6新增了兩個定義變量的關鍵字——let與const,用來取代var。
· let
let的用法與var類似,但let聲明的變量具有塊級作用域,即let聲明的變量只在當前代碼塊內有效,示例如下。

上述代碼中,分別使用var和let在一個代碼塊內聲明一個變量,之后在代碼塊外訪問這兩個變量,使用var聲明的變量可以正常訪問,使用let聲明的變量則報錯,這說明let聲明的變量只在當前代碼塊內有效。
· 暫時性死區(temporal dead zone)
let和const聲明的變量擁有暫時性死區(TDZ),即在進入它的作用域后,變量無法被訪問,直到聲明結束,示例如下。

上述代碼中,分別使用var和let以及非關鍵字的方式聲明一個變量,并在變量聲明前,嘗試訪問這個變量,使用var聲明的變量返回undefined,使用let和非關鍵字的方式聲明的變量拋出ReferenceError,這表明使用let聲明的變量不存在聲明提前。
之后,對a、b、c三個變量進行賦值操作,變量a和c順利完成賦值,并返回相應值,但對使用let聲明的變量b進行賦值時,拋出了異常,對照變量c,這表明變量b此時存在但不能進行賦值操作,也就是說變量b此時完成了聲明階段,但不能被正常訪問。
下面使用let聲明變量b,此后,再次訪問變量b時,盡管沒有對變量b進行賦值操作,但依然可以獲取變量b的值,這表明在聲明結束之前,變量b已經完成了聲明階段、初始化階段和賦值階段。
綜上,使用let聲明的變量在進入其作用域后,立即完成變量的聲明階段和初始化階段(如果有賦值操作,也會完成賦值階段),但在變量聲明結束前,無法對變量進行操作。
使用let聲明的變量在進入其作用域后,直到聲明結束前的這塊語法區間稱為“暫時性死區”(TDZ)。
聲明結束前不能訪問,意味著死區并不是基于空間的,而是基于時間的,由以下示例可以看出。

· const
const的用法和特性與let基本相同,不同之處在于,const聲明的是一個只讀常量,被const聲明的變量不能被重新聲明或賦值。換句話說,它將不能再被改變(對于引用類型的數據,其地址指向不可修改,屬性可修改),示例如下。

對于引用類型的數據,即便是將其地址指向修改為自身的地址也會拋出異常,示例如下。
const arr = [];
const arr2 = arr;
arr = arr2; //拋出異常> Uncaught TypeError: Assignment to constant variable
這個示例之后,聲明了一個變量arr,并將arr中存儲的堆中的地址賦值給另一個變量arr2,此時arr2和arr中存放的地址指向堆中的同一個對象,之后,將arr的地址修改為arr2的地址,即將arr的地址修改為自身的地址,此時,控制臺拋出異常,這表示被const聲明的數組或對象的地址指向不可修改。
4. 重復聲明
在相同作用域內,let和const聲明的變量不允許有重復聲明,示例如下。

3.2.3 非聲明變量
非聲明變量是指不使用關鍵字聲明的變量,示例如下。
b = 1; //嚴格模式下會拋出ReferenceError: b is not defined
console.log(window.b); // > 1
非聲明變量會被掛載到global對象(瀏覽器環境下,global對象指的是window對象)的屬性上,因此可以通過window訪問。
· var聲明變量和非聲明變量的區別
示例如下。

刪除情況,示例如下。
console.log(typeof a); // > string console.log(typeof b); // > undefined
delete操作符可以刪除一個對象的屬性,但如果屬性是一個不可配置(non-configurable)屬性,刪除時則會返回false(嚴格模式下刪除一個不可配置的變量會拋出異常)。
這就表示使用var聲明的變量是不可配置的,我們可以使用getOwnPropertyDescriptor方法來獲取描述屬性特性的對象,以此驗證這一點,示例如下。
Object.getOwnPropertyDescriptor(window, "a"); // -> {value: "a", writable: true, enumerable: true, configurable: false}
Object.getOwnPropertyDescriptor(window, "b"); // -> {value: "b", writable: true, enumerable: true, configurable: true}
兩者的根本區別在于關鍵字var聲明的變量是不可配置的,不能通過delete操作符刪除。
需要注意的是,configurable值一旦為false,描述屬性特性的對象就不能被修改,因此不能通過修改屬性描述符使var聲明的變量能被delete刪除,但反過來,可以使非聲明變量也不能被delete刪除,示例如下。

現在,你已經學會了多種聲明變量的方式,但不同的聲明方式應該在什么情況下使用呢?
建議是,盡量使用const,我們已經知道,let和const聲明的變量是不能被重復聲明的,但let所聲明的變量可以被重新賦值,const可以避免意外情況(例如,沒有使用關鍵字聲明變量)下對變量進行賦值操作,導致程序出現錯誤,而如果想要改變變量,就使用let聲明變量。
在ES6中,避免使用var。
練習
- 使用不同的方式聲明一個變量。
- Qt 5 and OpenCV 4 Computer Vision Projects
- ASP.NET Web API:Build RESTful web applications and services on the .NET framework
- Mastering Ember.js
- 網頁設計與制作教程(HTML+CSS+JavaScript)(第2版)
- Jupyter數據科學實戰
- 深入理解Elasticsearch(原書第3版)
- Learning OpenStack Networking(Neutron)
- Learning jQuery(Fourth Edition)
- Emgu CV Essentials
- Hands-On Nuxt.js Web Development
- Modern C++ Programming Cookbook
- Oracle 12c從入門到精通(視頻教學超值版)
- XML程序設計(第二版)
- 軟件再工程:優化現有軟件系統的方法與最佳實踐
- Kotlin入門與實戰