1. 程式人生 > >ES6裏關於作用域的拓展:塊級作用域

ES6裏關於作用域的拓展:塊級作用域

message div ssa col cau 也會 var 異常 true

  過去,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裏關於作用域的拓展:塊級作用域