1. 程式人生 > >《ECMAScript 6入門》筆記2

《ECMAScript 6入門》筆記2

塊級作用域

ES5只有全域性作用域和函式作用域,沒有塊級作用域,這帶來很多不合理的場景。

第一種場景,內層變數可能會覆蓋外層變數。

var tmp = new Date();

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}

f(); // undefined

上面程式碼的原意是,if程式碼塊的外部使用外層的tmp變數,內部使用內層的tmp變數。但是,函式f執行後,輸出結果為undefined,原因在於變數提升,導致內層的tmp變數覆蓋了外層的tmp變數。

第二種場景,用來計數的迴圈變數洩露為全域性變數。

var s = 'hello';

for (var i = 0; i < s.length; i++) {
  console.log(s[i]);
}

console.log(i); // 5

上面程式碼中,變數i只用來控制迴圈,但是迴圈結束後,它並沒有消失,洩露成了全域性變數。

ES6的塊級作用域

let實際上為JavaScript新增了塊級作用域。

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

上面的函式有兩個程式碼塊,都聲明瞭變數n,執行後輸出5。這表示外層程式碼塊不受內層程式碼塊的影響。如果兩次都使用var定義變數n,最後輸出的值才是10。

ES6允許塊級作用域的任意巢狀。

{{{{{let insane = 'Hello World'}}}}};

上面程式碼使用了一個五層的塊級作用域。外層作用域無法讀取內層作用域的變數。
{{{{
  {let insane = 'Hello World'}
  console.log(insane); // 報錯
}}}};

內層作用域可以定義外層作用域的同名變數。
{{{{
  let insane = 'Hello World';
  {let insane = 'Hello World'}
}}}};

塊級作用域的出現,實際上使得獲得廣泛應用的立即執行函式表示式(IIFE)不再必要了。
// IIFE 寫法
(function () {
  var tmp = ...;
  ...
}());

// 塊級作用域寫法
{
  let tmp = ...;
  ...
}

塊級作用域與函式宣告

ES5規定,函式只能在頂層作用域和函式作用域之中宣告,不能在塊級作用域宣告。

// 情況一
if (true) {
  function f() {}
}

// 情況二
try {
  function f() {}
} catch(e) {
  // ...
}

上面兩種函式宣告,根據ES5的規定都是非法的。

但是,瀏覽器沒有遵守這個規定,為了相容以前的舊程式碼,還是支援在塊級作用域之中宣告函式,因此上面兩種情況實際都能執行,不會報錯。

ES6引入了塊級作用域,明確允許在塊級作用域中宣告函式。ES6規定,塊級作用域之中,函式宣告語句的行為類似於let,在塊級作用域之外不可引用。

function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重複宣告一次函式f
    function f() { console.log('I am inside!'); }
  }

  f();
}());

上面程式碼在ES5中執行,會得到“I am inside!”,因為在if內宣告的函式f會被提升到函式頭部,實際執行的程式碼如下。
// ES5 環境
function f() { console.log('I am outside!'); }

(function () {
  function f() { console.log('I am inside!'); }
  if (false) {
  }
  f();
}());

ES6就完全不一樣了,理論上會得到“I am outside!”。因為塊級作用域內宣告的函式類似於let,對作用域之外沒有影響。但是,如果你真的在ES6瀏覽器中執行一下上面的程式碼,是會報錯的,這是為什麼呢?

原來,如果改變了塊級作用域內宣告的函式的處理規則,顯然會對老程式碼產生很大的影響。為了減輕因此產生的不相容問題,ES6在附錄B裡面規定,瀏覽器的實現可以不遵守上面的規定,有自己的行為方式。

    -允許在塊級作用域內宣告函式。

    -函式宣告類似於var,即會提升到全域性作用域或函式作用域的頭部。

    -同時,函式宣告還會提升到所在的塊級作用域的頭部。

注意,上面三條規則只對ES6的瀏覽器實現有效,其他環境的實現不用遵守,還是將塊級作用域的函式聲明當作let處理。

根據這三條規則,在瀏覽器的ES6環境中,塊級作用域內宣告的函式,行為類似於var宣告的變數。

// 瀏覽器的 ES6 環境
function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重複宣告一次函式f
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function

上面的程式碼在符合ES6的瀏覽器中,都會報錯,因為實際執行的是下面的程式碼。
// 瀏覽器的 ES6 環境
function f() { console.log('I am outside!'); }
(function () {
  var f = undefined;
  if (false) {
    function f() { console.log('I am inside!'); }
  }

  f();
}());
// Uncaught TypeError: f is not a function

考慮到環境導致的行為差異太大,應該避免在塊級作用域內宣告函式。如果確實需要,也應該寫成函式表示式,而不是函式宣告語句。
// 函式宣告語句
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 函式表示式
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}

ES6的塊級作用域允許宣告函式的規則,只在使用大括號的情況下成立,如果沒有使用大括號,就會報錯。
// 不報錯
'use strict';
if (true) {
  function f() {}
}

// 報錯
'use strict';
if (true)
  function f() {}

do表示式

本質上,塊級作用域是一個語句,將多個操作封裝在一起,沒有返回值。

{
  let t = f();
  t = t * t + 1;
}

上面程式碼中,塊級作用域將兩個語句封裝在一起。但是,在塊級作用域以外,沒有辦法得到t的值,因為塊級作用域不返回值,除非t是全域性變數。

現在有一個提案,使得塊級作用域可以變為表示式,也就是說可以返回值,辦法就是在塊級作用域之前加上do,使它變為do表示式。

let x = do {
  let t = f();
  t * t + 1;
};

上面程式碼中,變數x會得到整個塊級作用域的返回值。