ES6之塊級作用域

千鋒Python唐小強發表於2020-05-08

作用域

作用域指變數所作用的範圍,在 Javascript 中有兩種作用域:

  • 全域性作用域
  • 函式作用域

變數提升

變數提升(Hoisting)被認為是, Javascript 中執行上下文 (特別是建立和執行階段)工作方式的一種認識。具體表現就是所有透過 var 宣告的變數會提升到當前作用域的最前面。

function foo() { 

   console.log(temp);
}

function bar() {    
   console.log(temp);  
   var temp;
}

foo(); // ReferenceError: temp is not defined
bar(); // undefined

可以看到用 var 宣告瞭的並不會報錯。因為其實函式 bar 等同於

function bar() {    

   var temp;    
   console.log(temp);
}

大多數類 C 語言語法的語言都擁有塊級作用域。在一個程式碼塊(括在一對花括號中的一組語句)中定義的所有變數在程式碼塊的外部是不可見的。定義在程式碼塊中的變數在程式碼塊被執行結束後會變釋放掉。這是件好事。

糟糕的是,儘管 Javascript 的程式碼貌似支援塊級作用域,但實際上 Javascript 並不支援(就是因為有變數提升)。這個混淆之處可能成為錯誤之源。

所以在 ES6 中規定了 let 和 const 來支援塊級作用域。但是,是不是真的提升就不存在了呢,可以看下面暫時性死區這部分。

let

let 可以理解為『更完美的 var』,使用方法很簡單;

let foo = 3;

使用方法基本和 var 相同,而且宣告的變數只在其塊和子塊中可用,這點也與 var 相同。 二者之間最主要的區別在於 var 宣告的變數的作用域是整個封閉函式。

function foo() {

    if(true) {
        var temp = 5;
         console.log(temp);
    }

    console.log(temp);
}

function bar() {
    if(true) {
        let temp = 5;
        console.log(temp);
    }

    console.log(temp);
}

foo(); // 5 和 5
bar(); // 5 和 "ReferenceError: temp is not defined

let 宣告的變數的作用域只是外層塊,而不是整個外層函式。

我們可以利用這個特性來替代立即執行函式(IIFE)。

// IIFE

(function(){    
   var temp = xxx;    
   /*
       other code
   */}())

// 塊級

{  
   let temp = xxx;    
    /*
       other code
   */

}

const

const 的用法跟 let 差不多,但是 const 一定要初始化, 不初始化是會報錯的。

const temp = 4;// 沒有初始化報錯


const t; // SyntaxError: Missing initializer in const declaration

const 是塊級作用域,const 跟 let 的語義相似,就是用來宣告常量的,一旦宣告瞭就不能更改。值得注意的是 const 宣告的變數記錄的是指標,不可更改的是指標,如果 const 所宣告的是物件,物件的內容還是可以修改的。

// 重新賦值宣告導致報錯


const PI = 3.14;
PI = 3.1415926; // TypeError: Assignment to constant variable.

// 給物件增加屬性不會導致 obj 的指標變化,所以不會報錯

const obj = { foo: 2 };
obj.bar = 3;
console.log(obj); // {foo: 2, bar: 3}

暫時性死區

使用 let 或 const 宣告的變數,在宣告沒有到達之前,訪問該變數都會導致報錯,就連一直以為安全的 typeof 也不再安全。

// TDZ1


function foo() {    // TDZ 開始
   console.log(typeof temp);    
   let temp = 5; // TDZ 結束
}

foo(); // ReferenceError: temp is not defined

報的錯是 ReferenceError,如果使用 var 宣告的話,temp 輸出應該是 undefined,從 let 宣告的變數的塊的第一行,到宣告變數之間的這個區域被稱作暫時性死區(TDZ)。凡是在這個區域使用這些變數都會報錯。

// TDZ2


function bar() {    
   console.log(typeof temp);
}

bar(); // undefined

看到上面兩個例子仔細思考有沒有覺得想到點什麼?

在函式里沒有用 let 宣告 temp 的時候,temp 是 undefined,講道理在 let 宣告前也應該是 temp,然而 foo 函式卻報了錯,證明了就算是在未到達 let 宣告的地方,但是在用 let 之前已經起到了作用。這是不是說明其實 let 也有提升,只是在 TDZ 使用的時候報錯了,而不是 undefined。

事實上,當 JS 引擎檢視下面的程式碼塊有變數宣告時,對於 var 宣告的變數,會將宣告提升到函式或全域性作用域的頂部,而對 let 或 const 的時候會將宣告放在暫時性死區內。任何在暫時性死區內訪問變數的企圖都會導致“執行時”錯誤(runtime error)。只有執行到變數的宣告語句時,該變數才會從暫時性死區內被移除並可以安全使用。

禁止重複宣告

在同一個塊內,let 和 const 不能宣告相同的識別符號。禁止的情況包括:

  • let 或 const 和 let 或 const
  • var 和 let 或者 const
  • 函式引數與 let 或 const
// let 和 let

let foo = 1;
let foo = 2;

// let 和 const

let foo = 1;
const foo = 1;

// var 與 let

var foo = 1;
let foo = 1;

// 函式引數與 let

function bar(foo) {    
   let foo = 1;
}

以上情況都是會報 SyntaxError。但是在巢狀的作用域內使用 let 宣告同一變數是被允許的。

var foo = 1;


{  
   // 不會報錯
   let = 2;    
   // other code
}

同時因為是 let 和 const 是塊級作用域,宣告的變數在當前塊使用完之後就會被釋放,所以就算使用相同的識別符號也不會覆蓋外部作用域的變數, 而 var 是會覆蓋外部作用域的變數的。

function foo() {    

   var bar = 1;
   {        
       let bar = 2;
   }    
   console.log(bar);
}

function zoo() {    
   var bar = 1;
   {        
       var bar = 2;
   }    
   console.log(bar);
}

foo(); // 1
zoo(); // 2

最佳實踐

在 ES6 的發展階段,被廣泛認可的變數宣告方式是:預設情況下應當使用 let 而不是 var 。對於多數 JS 開發者來說, let 的行為方式正是 var 本應有的方式,因此直接用 let替代 var 更符合邏輯。在這種情況下,你應當對需要受到保護的變數使用 const。

在預設情況下使用 const ,而只在你知道變數值需要被更改的情況下才使用 let 。這在程式碼中能確保基本層次的不可變性,有助於防止某些型別的錯誤。

思考題

兩個思考題,我會把答案放在評論中。請點選原文連結去看答案

// 思考題 1


switch (x) {  
   case 0:    
       let foo;    
       break;  
   case 1:    
       let foo; // TypeError for redeclaration.
       break;
}

// 思考題 2
function bar(){  
   var foo = 1;  
   if (true) {      
       let foo = (foo + 2);
  }
}
bar();


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69923331/viewspace-2690698/,如需轉載,請註明出處,否則將追究法律責任。

相關文章