一、var變量
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>var</title> <script> window.onload = function(){ var aLi = document.getElementsByTagName('li'); for (var i=0;i<aLi.length;i++){ /*將var改為let*/ aLi[i].onclick = function(){ alert(i); /*單擊任何標簽都輸出4*/ } } } </script> </head> <body> <ul> <li>0</li> <li>1</li> <li>2</li> <li>3</li> </ul> </body> </html>
二、let變量
ES5 只有全局作用域和函數作用域,沒有塊級作用域,這帶來很多不合理的場景,在ES6之前,大部分人會選擇使用閉包來解決這個問題,今天我們使用ES6提供的let來解決這個問題。
代碼大同小異,只需將上例子代碼for循環中的var改為let,即可實現的效果是點擊不同的<li>標簽,alert出其對應的索引值。
window.onload = function(){ var aLi = document.getElementsByTagName('li'); for (let i=0;i<aLi.length;i++){ aLi[i].onclick = function(){ alert(i); } }; }
let 關鍵字可以將變量綁定到所在的任意作用域中(通常是 { .. } 內部)。換句話說,let為其聲明的變量隱式地了所在的塊作用域。
就是 for
循環還有一個特別之處,就是循環語句部分是一個父作用域,而循環體內部是一個單獨的子作用域。
var和let的區別
1.函數作用域 vs 塊級作用域
var 和 let 第一點不同就是 let 是塊作用域,即其在整個大括號 {} 之內可見。如果使用 let 來重寫上面的 for 循環的話,會報錯
var:只有全局作用域和函數作用域概念,沒有塊級作用域的概念。但是會把{}內也假稱為塊作用域。
let:只有塊級作用域的概念 ,由 { } 包括起來,if語句和for語句里面的{ }也屬於塊級作用域。
/*for循環,for循環里面是父級作用域,循環體內是另一個*/
for( let i = 0 ; i < 3 ; i++ ){ let i = 'abc' //用var替代let會報錯提示已經定義,若沒有任何關鍵字則每次賦值給i,最后只會輸出一次abc console.log(i) // 輸出3次abc }
function varTest() { var x = 31; if (true) { var x = 71; // same variable! console.log(x); // 71 } console.log(x); // 71 } function letTest() { let x = 31; if (true) { let x = 71; // different variable console.log(x); // 71 } console.log(x); // 31 }
2.變量提升 vs 暫時性死區
let 和 var 的第二點不同是,在變量聲明之前就訪問變量的話,會直接提示 ReferenceError,而不像 var 那樣使用默認值 undefined:
var 存在變量提升,而 let,const(后面會提及)聲明的變量卻不存在變量提升,所以用 let 定義的變量一定要在聲明后再使用,否則會報錯。
①
<script> /*1.var變量*/ console.log(a); //undefined var a=1; b=10; console.log(b); //10 var b; /*2.let變量*/ console.log(c); // Uncaught ReferenceError: c is not defined let c=2; console.log(d); // Uncaught ReferenceError: d is not defined let d; </script> <script> var x = 5; // 初始化 x elem = document.getElementById("demo"); // 查找元素 elem.innerHTML = "x 為:" + x + ",y 為:" + y; // 顯示 x 和 y var y = 7; // 初始化 y </script> 結果輸出: x 為:5,y 為:undefined y 輸出了 undefined,這是因為變量聲明 (var y) 提升了,但是初始化(y = 7) 並不會提升,所以 y 變量是一個未定義的變量。 <script> a=5; show(); var a; function show(){}; 預解析: function show(){}; var a; a=5; show(); //需要注意都是函數聲明提升直接把整個函數提到執行環境的最頂端。 </script>
可以看出,雖然代碼中console調用a在前,聲明a在后,但是由於在js中,函數及變量的聲明都將被提升到函數的最頂部,也就是說(var聲明的)變量可以先使用再聲明。
ES6明確規定,如果區塊中存在let命令,這個區塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。所以在代碼塊內,使用let命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱 TDZ)。
②
let a = 'outside'; if(true) { console.log(a);//Uncaught ReferenceError: a is not defined let a = "inside"; }
當前作用域頂部到該變量聲明位置中間的部分,都是該let變量的死區,在死區中,禁止訪問該變量。由此,我們給出結論,let聲明的變量存在變量提升, 但是由於死區我們無法在聲明前訪問這個變量。
“暫時性死區”也意味着typeof不再是一個百分之百安全的操作,因為會使typeof報錯。
{ typeof name;//ReferenceError let name; }
只要塊級作用域內存在let命令,它所聲明的變量就“綁定”(binding)這個區域,不再受外部的影響。在代碼塊中,使用let命令聲明變量之前,該變量都是不可用的,這在語法上稱為“暫時性死亡”。
3.let不允許重復聲明變量
可以看出,var:變量可以多次聲明,而let不允許在相同作用域內,重復聲明同一個變量。
<script> if (true) { let a; let a; // Uncaught SyntaxError: Identifier 'a' has already been declared } if(true){ var d; var d; //不會報錯 } if (true) { var c; let c; // Uncaught SyntaxError: Identifier 'c' has already been declared } if (true) { let d; var d; // Uncaught SyntaxError: Identifier 'd' has already been declared } </script>
4.全局變量vs全局對象的屬性
ES5中全局對象的屬性與全局變量基本是等價的,但是也有區別,比如通過var聲明的全局變量不能使用delete從 window/global ( global是針對與node環境)上刪除,不過在變量的訪問上基本等價。
ES6 中做了嚴格的區分,使用 var 和 function 聲明的全局變量依舊作為全局對象的屬性,使用 let
, const
命令聲明的全局變量不屬於全局對象的屬性。
<script> var a = 10; console.log(window.a); //10 console.log(this.a) //10 let b = 20; console.log(window.b); // undefined console.log(this.b) // undefined </script>
三、const聲明的常量
除了let以外,ES6還引入了cons,const 和 let 的作用域是一致的,不同的是 const 變量一旦被賦值,就不能再改變了,但是這並不意味着使用 const 聲明的變量本身不可變,只是說它不可被再次賦值了,而且const 聲明的變量必須經過初始化。
const a = 1; a = 2; // // Uncaught TypeError: Assignment to constant variable const b; // Uncaught SyntaxError: Missing initializer in const declaration
注:復合類型const變量保存的是引用。因為復合類型(如數組和對象)的常量不指向數據,而是指向數據(heap)所在的地址(stack),所以通過 const 聲明的復合類型只能保證其地址引用不變,但不能保證其數據不變。所以將一個對象聲明為常量必須非常小心。
簡單數據類型(數值,字符串,布爾值):值保存在變量指向的那個內存地址,因此等同於常量。
復合類型的數據(對象和數組):變量指向的是內存地址,保存的是一個指針,const只能保存這個指針地址是固定的,至於他指向的數據結構是不是可變的,就完全不能控制了。
<script> /*不會報錯,因為names1指向的地址不變,改變的只是內部數據*/ const names1 = []; names1[0] = 1; names1[1] = 2; names1[2] = 3; names1[3] = 10; console.log(names1); /*出錯,因為變量names2指向的地址不能發生改變,應始終指向[]所在的地址,[1,4]與[6,7]不是同一個地址*/ const names2=[1,4]; names2=[6,7]; //報錯 </script>
最后
但是什么時候用 var、let 或 const 呢?我的建議是,大多數情況下都使用 const,除非你知道你的變量的值還會被改變,以上大概是總結后的內容,看來,還是多用 let 、const 吧。
部分參考資料來源:https://www.cnblogs.com/slly/p/9234797.html