JavaScript三大變量聲明詳析
前言
ECMAScript 變量是松散類型的,變量可以用於保存任何類型的數據,每個變量隻不過是一個用於保存任意值的命名占位符。
本文主要講述 Javascript 中三大變量聲明的用法與寫法,有 3 個關鍵字可以聲明變量:var
、const
和 let
。其中,var
在ECMAScript
的所有版本中都可以使用,而 const
和 let
隻能在 ECMAScript 6
及更晚的版本中使用。(現在最新版為 ECMAScript 12
)
Var
var 操作符用於定義變量(var 也是一個關鍵字),後跟變量名(即標識符)
基礎寫法
聲明未定義值
這裡定義瞭一個名為test的變量,可以用它保存任何類型的值;
var test;
註意: 不初始化的情況下,變量會保存一個特殊值
undefined
聲明定義值
ECMAScript
實現變量初始化,因此可以同時定義變量並設置它的值,如下:
// 聲明定義值 var test = "good job";
像這樣的初始化變量不會將它標識為字符串類型,隨時可以改變保存的值,從而改變值的類型。
不推薦寫法
在下面這個例子中,變量 test
首先被定義為一個保存字符串值的變量,然後又被重寫為保存瞭數值:
var test = "good job"; test = 299; //合法,但不推薦 /* 像這種單獨的變量可以直接賦值就沒必要另起去改瞭 沒必要 */
雖然不推薦改變變量保存值的類型,但這在 ECMAScript
中是完全有效的。
var 聲明作用域
使用 var
定義的變量會成為包含它的函數的局部變量,簡稱為作用域變量。
局部作用域
使用 var
在一個函數內部定義一個變量,就意味著該變量將在函數退出時被銷毀,如下:
function fun() { var test = "Hello World"; // 局部變量 } fun(); console.log(test); // 出錯!
這裡,test
變量是在函數內部使用 var 定義的,函數為 fun()
,調用它會創建這個變量並給它賦值;調用之後變量隨即被銷毀,因此示例中的最後一行會導致錯誤。
全局作用域
在函數內定義變量時省略 var 操作符,可以創建一個全局變量,如下
function fun() { test = "Hello World"; // 局部變量 } fun(); console.log(test); // "Hello World"
去掉之前的 var 操作符之後,test
就變成瞭全局變量;隻要調用一次函數 fun()
,就會定義這個變量,並且可以在函數外部訪問到。
註意:雖然可以通過省略
var
操作符定義全局變量,但不推薦這麼做。在嚴格模式下,如果像這樣給未聲明的變量賦值,則會導致拋出 ReferenceError。
便捷語法
如果需要定義多個變量,可以在一條語句中用逗號分隔每個變量(及可選的初始化),如下:
var name = "hey~~", name = "shrimpsss", age = 99;
這裡定義並初始化瞭 3 個變量,因為 ECMAScript
是松散類型的,所以使用不同數據類型初始化的變量可以用一條語句來聲明; 雖然插入換行和空格縮進並不是必需的,但這樣有利於閱讀理解。
註意: 在嚴格模式下,不能定義名為
eval
和arguments
的變量,否則會導致語法錯誤。
var 聲明提升
var 這個關鍵字在函數中聲明變量時會自動提升到函數作用域頂部,如下:
function foo() { console.log(age); var age = 26; } foo(); // undefined
之所以不會報錯,是因為 ECMAScript
運行時把它看成等價於如下代碼:
function foo() { var age; console.log(age); age = 26; } foo(); // undefined
這就所謂的“提升”(hoist),也就是把所有變量聲明都拉到函數作用域的頂部。
Let
let
跟 var
的作用差不多,但 let
聲明的范圍是塊作用域(var
聲明的范圍是函數作用域)
塊作用域是函數作用域的子集,因此適用於 var
的作用域限制同樣也適用於 let
let 作用域
let 作用域隻能在塊作用域裡訪問,不能跨塊訪問,也不能跨函數訪問
兩者的對比會更加凸顯不一,如下:
// --- var --- if (true) { var name = 'king'; console.log(name); // Matt } console.log(name); // Matt // --- let --- if (true) { let age = 26; console.log(age); // 26 } console.log(age); // ReferenceError: age 沒有定義
在這裡,age 變量之所以不能在 if 塊外部被引用,是因為它的作用域僅限於該塊內部。
冗餘聲明
let 不允許同一個塊作用域中出現冗餘聲明
var name; var name; let age; let age; // SyntaxError;標識符 age 已經聲明過瞭
冗餘聲明的報錯不會因混用 let
和 var
而受影響。
這兩個關鍵字聲明的並不是不同類型的變量,它們隻是指出變量在相關作用域如何存在,如下:
var name; let name; // SyntaxError let age; var age; // SyntaxError
JavaScript 引擎會記錄用於變量聲明的標識符及其所在的塊作用域,因此嵌套使用相同的標識符不會報錯。
暫時性死區
let
與 var
的另一個重要的區別,就是 let
聲明的變量不會在作用域中被提升
// name 會被提升 console.log(name); // undefined var name = 'vito'; // age 不會被提升 console.log(age); // ReferenceError:age 沒有定義 let age = 26
在解析代碼時,JavaScript 引擎也會註意出現在塊後面的 let
聲明,隻不過在此之前不能以任何方式來引用未聲明的變量。
在 let 聲明之前的執行瞬間被稱為“暫時性死區”(temporal dead zone),在此階段引用任何後面才聲明的變量都會拋出
ReferenceError
。
全局聲明
與 var
關鍵字不同,使用 let
在全局作用域中聲明的變量不會成為 window
對象的屬性
/* window.xx:在window對象中查找此屬性(元素)*/ var name = 'vito'; console.log(window.name); // 'vito' let age = 26; console.log(window.age); // undefined
但 let
聲明仍然是在全局作用域中發生的,所以相應變量會在頁面的生命周期內存續。
條件聲明
因為 let 的作用域是塊,所以不可能檢查前面是否已經使用 let 聲明過同名變量,同時也就不可能在沒有聲明的情況下聲明它,沒有與var
一樣的聲明提升。
/* 方便理解: 把兩個<script>代碼塊看做一個代碼塊 */ <script> var name = 'Nicholas'; let age = 26; </script> <script> // 假設腳本不確定頁面中是否已經聲明瞭同名變量 // 那它可以假設還沒有聲明過 var name = 'Matt'; // 這裡沒問題,因為可以被作為一個提升聲明來處理 // 不需要檢查之前是否聲明過同名變量 let age = 36; // 如果 age 之前聲明過,這裡會報錯 </script>
為此, let
這個新的 ES6
聲明關鍵字,不能依賴條件聲明模式
註意:
let
不能進行條件式聲明是件好事,因為條件聲明是一種反模式,它讓程序變得更難理解
for 循環中的 let
在 let 出現之前,for 循環定義的迭代變量會滲透到循環體外部。
常見for循環
如下,用 var 示例:
for (var i = 0; i < 5; ++i) { // 循環邏輯 } console.log(i); // 5
改成使用 let
就不會有這個問題瞭,因為 let
迭代變量的作用域僅限於 for 循環塊內部:
for (let i = 0; i < 5; ++i) { // 循環邏輯 } console.log(i); // ReferenceError: i 沒有定義 復制代碼
for循環套定時器
在使用 var 的時候,最常見的問題就是對迭代變量的奇特聲明和修改,如下:
for (var i = 0; i < 5; ++i) { setTimeout(() => console.log(i), 0) } // 實際上會輸出 5、5、5、5、5
怎麼樣,是不是以為會輸入 0、1、2、3、4 ?
之所以會這樣,是因為在退出循環時,迭代變量保存的是導致循環退出的值:5。在之後執行超時邏輯時,所有的 i 都是同一個變量,因而輸出的都是同一個最終值。
而在使用 let 聲明迭代變量時,JavaScript 引擎在後臺會為每個迭代循環聲明一個新的迭代變量。
for (let i = 0; i < 5; ++i) { setTimeout(() => console.log(i), 0) } // 會輸出 0、1、2、3、4
每個 setTimeout
引用的都是不同的變量實例,所以 console.log
輸出的是我們期望的值,也就是循環執行過程中每個迭代變量的值。
這種每次迭代聲明一個獨立變量實例的行為適用於所有風格的
for
循環,包括for-in
和for-of
循環。
const
const跟 let 的行為差不多,但區別是用它聲明變量時必須同時初始化變量,且嘗試修改 const 聲明的變量會導致運行時錯誤(一致性)
const 限制
- const 不可改值
const age = 26; age = 36; // TypeError
- const 也不允許重復聲明
const name = 'Lisa'; const name = 'Vito'; // SyntaxError
- const 聲明的作用域也是塊
const name = 'apple'; if (true) { const name = 'avocado'; } console.log(name); // apple
const
聲明的限制隻適用於它指向的變量的引用。 換句話說,如果 const
變量引用的是一個對象,那麼修改這個對象內部的屬性並不違反 const
的限制,如下:
const person = {}; person.name = 'vito'; // ok
怎麼樣,是不是很神奇 o( ̄▽ ̄)d
for 循環中的 const
JavaScript 引擎會為 for
循環中的 let
聲明分別創建獨立的變量實例,雖然 const
變量跟 let
變量很相似,但是不能用 const
來聲明迭代變量(因為迭代變量會自增):
for (const i = 0; i < 10; ++i) {} // TypeError 類型錯誤
不過,如果你隻想用 const
聲明一個不會被修改的 for
循環變量,那也是可以的。
每次迭代隻是創建一個新變量,這對 for-of
和 for-in
循環特別有意義的,如下:
let i = 0; for (const j = 7; i < 5; ++i) { console.log(j); } // 7, 7, 7, 7, 7 for (const key in {a: 1, b: 2}) { console.log(key); } // a, b for (const value of [1,2,3,4,5]) { console.log(value); } // 1, 2, 3, 4, 5
聲明風格及最佳實踐
ECMAScript 6
增加 let
和 const
為這門語言更精確地聲明作用域和語義提供瞭更好的支持
- 不使用
var
有瞭 let 和 const,大多數開發者會發現自己不再需要 var 瞭。限制自己隻使用 let
和 const
有助於提升代碼質量,因為變量有瞭明確的作用域、聲明位置,以及不變的值。
const
優先,let
次之
使用 const
聲明可以讓瀏覽器運行時強制保持變量不變,也可以讓靜態代碼分析工具提前發現不合法的賦值操作。因此,應該優先使用 const
來聲明變量,隻在提前知道未來會有修改時,再使用 let。
總結
ECMAScript
變量是松散類型的,變量可以用於保存任何類型的數據var
定義的變量,沒有塊的概念,可以跨塊訪問, 不能跨函數訪問。let
定義的變量,隻能在塊作用域裡訪問,不能跨塊訪問,也不能跨函數訪問。const
用來定義常量,使用時必須初始化(即必須賦值),隻能在塊作用域裡訪問,而且不能修改。- 對於
let
與const
同一個變量隻能使用一種方式聲明,不然會報錯 - 定義變量優先使用
const
,次之let
,養成良好代碼規范
到此這篇關於JavaScript三大變量聲明詳析的文章就介紹到這瞭,更多相關JS變量聲明內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 基於JavaScript ES新特性let與const關鍵字
- 簡單談談JavaScript變量提升
- JavaScript es6中var、let以及const三者區別案例詳解
- 一文搞懂JavaScript中的this綁定規則
- 淺談JS中var,let和const的區別