前面的話
過去,javascript缺乏塊級作用域,var聲明時的聲明提升、屬性變量等行為讓人困惑。ES6的新語法可以幫助我們更好地控制作用域。本文將詳細介紹ES6新引入的塊級作用域綁定機制、let和const聲明機制及最佳實踐
var聲明
【變量提升】
var
聲明會發生”變量提升“現象,即變量可以在聲明之前使用,值為undefined
function getValue(condition){ if(condition){ var value = 'blue'; return value; }else{
//此處可訪問變量value,值為undefined return null; }
//此處可訪問變量value,值為undefined }
如果沒有javascript開發經驗,可能會認為只有condition為true時,才會創建變量value
但實際上,在預編譯階段,javascript引擎會將上面的函數修改成下面這樣
function getValue(condition){ var value; if(condition){ value = 'blue'; return value; }else{ return null; } }
變量value的聲明被提升到函數頂部,而初始化操作依然留在原處。如果不注意,很可能引起錯誤。為些,ES6引入了塊級作用域來強化對變量生命周期的控制
【塊級聲明】
塊級聲明用于聲明在指定塊的作用域之外無法訪問的變量,它存在于
1、函數內部
2、{}之間的塊區域內
let聲明
let聲明的用法與var聲明相同。用let代替var來聲明變量,就可以把變量的作用域限制在當前代碼塊中
function getValue(condition){ if(condition){ let value = 'blue'; return value; }else{ //變量value在此處不存在 return null; } //變量value在此處不存在 }
變量value改由關鍵字let進行聲明后,不再被提升到函數頂部。執行流離開if塊時,value立刻被銷毀。如果condition的值為false,就永遠不會聲明并初始化value
【禁止重聲明】
假設作用域中已經存在某個標識符,此時再使用let關鍵字聲明它就會拋出錯誤
var count = 30; //拋出語法錯誤 //Uncaught SyntaxError: Identifier 'count' has already been declared let count = 40;
const聲明
使用const聲明的是常量,其值一旦被設定后不可更改。因此,每個通過const聲明的常量必須進行初始化
const num = 30; //拋出語法錯誤 //Uncaught SyntaxError: Missing initializer in const declaration const name;
const與let聲明老師塊級標識符,所以常量也只在當前代碼塊中有效,一旦執行到塊外會立即被銷毀。常量同樣也不會被提升到作用域頂部
if(condition){ const num = 30; } //此處無法訪問num
【禁止重聲明】
與let類似,在同一作用域內用const聲明已經存在的標識符也會導致語法錯誤,無論該標識符是使用var,還是let聲明的
var message = 'hello'; let num = 10; //這兩條語句都會拋出錯誤 const message = "goobye"; const num = 30;
【無法再賦值】
const與let聲明最大的不同之處在于,const聲明的常量無法再賦值
let num1 = 10; num1= 20; const num2 = 10; //Uncaught TypeError: Assignment to constant variable. num2 = 20;
【可修改對象屬性】
const聲明不允許修改綁定,但允許修改值。這也就意味著用const聲明對象后,可以修改該對象的屬性值
const person = { name: 'huochai' }; //可以修改對象屬性的值 person.name = 'match'; //Object {name: "match"} console.log(person); //拋出語法錯誤 //Uncaught TypeError: Assignment to constant variable. person = { name: 'match' }
臨時死區
與var不同,let和const聲明的變量不會被提升到作用域頂部,如果在聲明之前訪問這些變量,會引發錯誤。而從作用域頂部到聲明變量語句之前的這個區域,被稱為臨時死區(temporal dead zone),簡稱為TDZ
if(true){ //undefined console.log(typeof value); var value = "blue"; } if(true){ //Uncaught ReferenceError: value is not defined console.log(typeof value); let value = "blue"; }
但是,在let或const聲明的作用域之外使用該變量就不會報錯
//undefined console.log(typeof value); if(true){ let value = "blue"; }
循環綁定
【var聲明】
長久以來,var聲明使得在循環中創建函數異常困難,因為變量到了循環之外仍能訪問
var funcs = []; for(var i = 0; i < 10; i++){ funcs.push(function(){ //輸出10次10 console.log(i); }); } funcs.forEach(function(func){ func(); })
上面代碼中,預期的結果是輸出數字0-9,但它卻一連串輸出了10次10,這是因為循環里的每次迭代同時共享著變量i,循環內部創建的函數全都保留了對相同變量的引用,循環結束時變量i的值為10,所以每次調用console.log(i)時就會輸出10
【IIFE】
為解決這個問題,可以在循環中使用立即調用函數表達式(IIFE),以強制生成計數器變量的副本
var funcs = []; for(var i = 0; i < 10; i++){ funcs.push((function(value){ return function(){ //0 //1 //... //9 console.log(value); } })(i)); } funcs.forEach(function(func){ func(); })
在循環內部,IIFE表達式為接受的每一個變量i都創建了一個副本并存儲為變量value,這個變量的值就是相應迭代創建的函數所使用的值,因此調用每個函數都會像從0-9循環一樣得到期望的值
【let】
let聲明模仿上例中IIFE所做的一切來簡化循環過程。每次迭代循環都會創建一個新變量,并以之前迭代中同名變量的值將其初始化
var funcs = []; for(let i = 0; i < 10; i++){ funcs.push(function(){ //0 //1 //... //9 console.log(i); }); } funcs.forEach(function(func){ func(); })
以上這段循環相比之下更為簡潔,每次循環時let聲明都會創建一個新變量i,并將其初始化為i的當前值,所以循環內部創建的每個函數都能得到屬性它們自己的i的副本
對于for-in循環和for-of循環來說也是一樣的
var funcs = []; obj = { a:true, b:true, c:true } for(let key in obj){ funcs.push(function(){ //a //b //c console.log(key); }) } funcs.forEach(function(func){ func(); })
【const】
對于const聲明來說,由于其無法改變變量的值,所以無法使用普通的for循環
var funcs = []; for(const i = 0; i < 10; i++){ funcs.push(function(){ //Uncaught TypeError: Assignment to constant variable. console.log(i); }); } funcs.forEach(function(func){ func(); })
由于for-in循環中每次迭代不會修改已有綁定,而是創建一個新綁定,所以在for-in循環中可以使用const
var funcs = []; obj = { a:true, b:true, c:true } for(const key in obj){ funcs.push(function(){ //a //b //c console.log(key); }) } funcs.forEach(function(func){ func(); })
屬性變量
對var聲明的變量來說,如果處于全局作用域,它們會自動成為window對象的屬性。這意味著用var很可能無意中覆蓋一個已經存在的全局變量
//function RegExp() { [native code] } console.log(RegExp); var RegExp = "hello"; console.log(RegExp);//'hello' console.log(window.RegExp);//'hello'
如果使用let或const聲明的變量,不會成為window對象的屬性
let RegExp = "hello"; console.log(RegExp);//'hello' console.log(window.RegExp);//function RegExp() { [native code] }
因此,如果希望在window對象下定義變量,要使用var聲明。如果不希望,則使得let或const
最佳實踐
默認使用const,只有確實需要改變變量的值時使用let
因為大部分變量的值在初始化后不應再改變,而預料外的變量值的改變是很多bug的源頭
文章列表