在javascript中,我們都知道使用var來聲明變量。javascript是函數級作用域,函數內可以訪問函數外的變量,函數外不能訪問函數內的變量。
函數級作用域會導致一些問題就是某些代碼塊內的變量會在全局范圍內有效,這我們是非常熟悉的:
1 for (var i = 0; i < 10; i++) { 2 console.log(i); // 0,1,2...,9 3 } 4 console.log(i); //10 5 6 if(true){ 7 var s = 20; 8 } 9 console.log(s); //20
在es6中增加了let(變量)和const(常量)來聲明變量,使用的是塊級作用域,變量聲明只在代碼塊內有效:
1 for (let i = 0; i < 10; i++) { 2 console.log(i); // 0,1,2...,9 3 } 4 console.log(i); //ReferenceError: i is not defined 5 6 if(true){ 7 let s = 20; 8 } 9 console.log(s); //ReferenceError: s is not defined
我們最常遇到的循環事件綁定i產生的閉包問題,是因為i綁定在全局,每次訪問都會取到全局的i的值,所以訪問哪個元素,得到的i都是循環體最后一輪的i。我們通常的解決辦法是借助IIFE。在es6中let定義的i只在循環體內有效,所以每次循環i都是一個新的變量,於是會產生正常的輸出:
1 //使用var 2 var arr1 = []; 3 for (var i = 0; i < 10; i++) { 4 arr1[i] = function() { 5 console.log(i); 6 } 7 } 8 9 arr1[5](); //10 10 arr1[8](); //10 11 12 //使用let 13 var arr2 = []; 14 for (let i = 0; i < 10; i++) { 15 arr2[i] = function() { 16 console.log(i); 17 } 18 } 19 20 arr2[5](); //5 21 arr2[8](); //8
變量提升:
在es5中var定義的變量存在變量提升,es6中使用的let不存在變量提升:
1 console.log(i); 2 var i = 10; //10 3 4 console.log(s); 5 let s = 20; //ReferenceError: can't access lexical declaration `s' before initialization
函數提升:
es5中無論if語句是否執行,在if語句內的函數聲明都會提升到當前的作用域的頂部而得到執行,es6支持塊級作用域,無論是否進入if,內部的函數都不會影響到外部的函數
嚴格模式下函數聲明只能在頂層環境和函數體內,不能if或者循環體內進行函數聲明
1 //es5環境 2 function f(){ 3 console.log('outside'); 4 } 5 if(true){ 6 function f(){ 7 console.log('inside'); 8 } 9 } 10 11 f(); //inside 12 13 14 //es6環境 15 function d(){ 16 console.log('outside'); 17 } 18 if(true){ 19 function d(){ 20 console.log('inside'); 21 } 22 } 23 24 d(); //outside
每個代碼塊中即同一個作用域中,每個變量只能使用let定義一次,使用var重復定義會進行覆蓋,但是使用let重復定義會報錯,不同代碼塊可以重復定義:
1 let i = 10; 2 3 if(true){ 4 let i = 20; 5 console.log(i); //20 6 let i = 50; //redeclaration of let i 7 console.log(i); 8 }
暫時性死區:
在代碼塊內,使用let聲明變量之前,該變量都不可用,即是變量在使用賦值之前需要先進行聲明,每個代碼塊會進行變量綁定,以確定變量屬於哪個環境,在綁定之前進行變量的操作都是非法的:
1 let i = 10; 2 3 if(true){ 4 i = 50; //ReferenceError 5 let i = 20; 6 console.log(i); 7 } 8 //因為在if中聲明了i,i綁定了if的環境,所以在if的i聲明之前,i都不可用,導致i=50報錯
IIFE有一個重要的作用是封裝變量,避免變量泄漏到全局,塊級作用域的出現,其實就使得IIFE不再必要了:
1 ~function(){ 2 if(true){ 3 var i = 10; 4 } 5 }() 6 console.log(i); //ReferenceError: i is not defined 7 8 if(true){ 9 let s = 20; 10 } 11 12 console.log(s); //ReferenceError: s is not defined
let和const的區別:
let聲明變量,值可以改變。const聲明常量,值不可改變(使用const時,常量最好使用大寫)
其他的屬性let與const一致
1 let a = 10; 2 a = 20; 3 console.log(a); //20 4 5 const B = 30; 6 B = 40; //TypeError: invalid assignment to const `b' 7 console.log(B);
const只保證常量名指向的地址不變,並不能保證地址的數據不變,如果想吧對象凍結數據不可改變,可使用Object.freeze(obj)
1 const a = {}; 2 a.name = 'gary'; 3 console.log(a.name); //gary 4 5 const b = Object.freeze({}); 6 b.name = 'bob'; 7 console.log(b.name); //undefined
在es5中我們都知道,在全局定義變量其實就是等於在全局對象設置屬性,全局對象屬性與全局變量是等價的。全局變量的賦值和全局屬性的賦值是同一件事
在es6中修改可這一規定,使用let和const和class聲明的全局變量與全局對象不等價
1 var a = 10; 2 console.log(a === window.a); //true 3 4 let b = 20; 5 console.log(b === window.b); //false