JavaScript:剖析ES6(1)--let和const

最近在寫GICXMLLayout開源庫的時候要支持JavaScript,而在實現的過程當中對於ES6的實現原理也有了進一步的瞭解,所以寫幾篇博客,已作記錄。javascript

注意:java

文中出現的結論,只能表明是在使用babel編譯的狀況下的結論babel

1、let

先從一個簡單的例子開始。閉包

例子1

let a = 1;
console.log(a);
複製代碼

通過babel編譯後的代碼以下:函數

var a = 1;
console.log(a);
複製代碼

咱們會發現,let在這樣的場景下,跟var是沒有區別的。oop

例子2

那若是是這樣呢?ui

{
    let a = 1;
    console.log(a);
}
console.log(a);
複製代碼

通過babel編譯後的代碼以下:spa

{
  var _a = 1;
  console.log(_a);//2
}
console.log(a);// ReferenceError
複製代碼

運行上面的代碼,對於大括號外面的console.log(a);會直接報ReferenceError錯誤。之因此會出現這樣的狀況,是由於babel在編譯的時候將let a編譯成了var _a,而且將同級做用域內的變量引用一併改成_a,而做用域外的引用沒有改變。code

例子3 (變量提高)

這個例子是有關let變量提高ip

console.log(bar);
let bar = 2;
複製代碼

通過babel編譯後的代碼以下:

console.log(bar);
var bar = 2;
複製代碼

從這裏能夠看出,let聲明的變量,其實仍是存在變量提高的問題的。並無像ES6規範中提到的那樣let能夠阻止變量提高。然而若是你使用以下代碼就又不同了。

console.log(bar);
{
    let bar = 2;
}
複製代碼

通過babel編譯後的代碼以下:

console.log(bar);// ReferenceError
{
  var _bar = 2;
}
複製代碼

運行這樣的代碼你就會獲得一個ReferenceError的錯誤。看起來好像是阻止了變量提高。但咱們仔細分析下的話,這徹底是由於let在一個塊級做用域內定義了,而babel在編譯的時候只是將變量名稱重命名了而已。

從上面的幾個例子也進一步能夠分析出,let的所謂塊級做用域,簡單理解是在同一個做用域內引用的變量名稱,在編譯的時候被重命名了,而做用域外的變量名不會被重命名,由此引出的結果是,因爲變量名被重命名了,所以,對於做用域外的變量名就會報ReferenceError的錯誤。這也就引出了let塊級做用域暫時性死區等一系列特性。

例子4(循環迭代)

這個例子是有關循環的例子。

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6
複製代碼

若是你把上面的代碼中let換成var那麼a[6]();輸出的將會是10。之因此這樣,咱們分析下通過babel編譯後的代碼:

var a = [];
var _loop = function (i) {
  a[i] = function () {
    console.log(i);
  };
};
for (var i = 0; i < 10; i++) {
  _loop(i);
}
a[6]();
複製代碼

咱們能夠看到,babelfor循環內的代碼單獨提取出來了,咱們知道閉包能夠捕獲父級function的變量,而且咱們也知道對於number這樣的基本數據類型,JS在傳參的時候是直接拷貝的,而不是引用。所以對於_loop這個方法,每次傳過來的i都會被拷貝一份,而閉包捕獲的變量僅僅是一個已經被拷貝的變量而已,也便是變量的地址已經改變,不是for循環中i的地址了。在沒有let的時候,要解決這樣的問題,咱們採用的方法每每也是使用閉包(當即執行函數)來實現。

例子5

var tmp = 123;
if (true) {
  {
    tmp = 'abc'; 
  }
  let tmp;
}

複製代碼

通過babel編譯後的代碼以下:

var tmp = 123;
if (true) {
  {
    _tmp = 'abc';//ReferenceError
  }
  var _tmp;
}
複製代碼

運行上面的例子你會獲得一個ReferenceError的錯誤。這個例子充分說明了let關鍵字的塊級做用域的功能,只要是在同級做用域內,全部引用了相同變量名的地方都會被編譯成新的變量名

咱們能夠得出一個結論。

let的塊級做用域的本質就是經過babel重命名變量

例子6

let a = 10;
  var a = 1;
複製代碼

這樣的代碼,你連編譯都沒法編譯,ES6規定在同級做用域內不容許存在相同的變量名,所以babel直接在編譯期就報錯了。

2、const

const的原理跟let其實差很少。可是多了一個不可重複賦值。

例子1

const PI = 3.1415;
PI = 10;
複製代碼

通過babel編譯後的代碼以下:

function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); }

var PI = 3.1415;
PI = (_readOnlyError("PI"), 10);
複製代碼

咱們能夠從編譯後的代碼中看到,當咱們試圖對一個const的變量賦值的時候,babel直接將賦值代碼替換成直接調用_readOnlyError方法來拋出異常。

例子2

const必須在聲明變量的時候就賦值。若是咱們不賦值呢?好比下面

const PI;
複製代碼

你會發現,沒法完成編譯。babel直接在編譯期就作了檢查。

總結

letconst在編譯後仍是以var來聲明變量,不一樣的地方在於,使用letconst聲明的變量,若是在上下文環境中存在相同變量名的var,那麼會自動將let聲明的變量名改爲其餘名字,簡單說就是對變量名在編譯期進行重命名。而正是由於這樣的重命名的改動,由此引出了不少ES6對於let的其餘一些特性,好比:塊級做用域暫時性死區等等。

注意:重命名不是let實現的所有

相關文章
相關標籤/搜索