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