ES6之塊級做用域

歡迎訪問我的站點javascript

做用域

做用域指變量所做用的範圍,在 Javascript 中有兩種做用域:java

  • 全局做用域
  • 函數做用域

變量提高

變量提高(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 並不支持(就是由於有變量提高)。這個混淆之處可能成爲錯誤之源。ui

因此在 ES6 中規定了 let 和 const 來支持塊級做用域。可是,是否是真的提高就不存在了呢,能夠看下面暫時性死區這部分。spa

let

let 能夠理解爲『更完美的 var』,使用方法很簡單;指針

let foo = 3;
複製代碼

使用方法基本和 var 相同,並且聲明的變量只在其塊和子塊中可用,這點也與 var 相同。 兩者之間最主要的區別在於 var 聲明的變量的做用域是整個封閉函數。code

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 聲明的變量的做用域只是外層塊,而不是整個外層函數。cdn

咱們能夠利用這個特性來替代當即執行函數(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 也有提高(這個提高並非 var 的那種提高,只是有影響),只是在 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 = 2;

// var 與 let
var foo = 1;
let foo = 2;

// 函數參數與 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();
複製代碼

你們好,我是桃翁,我爲本身代言!

我的微信公衆號

我的微信公衆號
相關文章
相關標籤/搜索