ES6裏關於作用域的拓展:塊級作用域
過去,javascript缺乏塊級作用域,var聲明時的聲明提升、屬性變量等行為讓人困惑。ES6的新語法可以幫助我們更好地控制作用域。
一、var聲明
1、變量提升: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引入了塊級作用域來強化對變量生命周期的控制
2、塊級聲明
塊級聲明用於聲明在指定塊的作用域之外無法訪問的變量,它存在於
(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
1、禁止重聲明
假設作用域中已經存在某個標識符,此時再使用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聲明均是塊級標識符,所以常量也只在當前代碼塊中有效,一旦執行到塊外會立即被銷毀。常量同樣也不會被提升到作用域頂部
1、禁止重聲明
與let類似,在同一作用域內用const聲明已經存在的標識符也會導致語法錯誤,無論該標識符是使用var,還是let聲明的
var message = ‘hello‘;
let num = 10;
//這兩條語句都會拋出錯誤
const message = "goobye";
const num = 30;
2、無法再賦值
const與let聲明最大的不同之處在於,const聲明的常量無法再賦值
let num1 = 10;
num1= 20;
const num2 = 10;
//Uncaught TypeError: Assignment to constant variable.
num2 = 20;
3、可修改對象屬性
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";
}
五、循環綁定
1、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
2、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循環一樣得到期望的值
3、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();
})
4、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的源頭
ES6裏關於作用域的拓展:塊級作用域