最近在寫GICXMLLayout
開源庫的時候要支持JavaScript
,而在實現的過程當中對於ES6
的實現原理也有了進一步的瞭解,所以寫幾篇博客,已作記錄。javascript
注意:java
文中出現的結論,只能表明是在使用babel編譯的狀況下的結論babel
let
先從一個簡單的例子開始。閉包
let a = 1;
console.log(a);
複製代碼
通過babel
編譯後的代碼以下:函數
var a = 1;
console.log(a);
複製代碼
咱們會發現,let
在這樣的場景下,跟var
是沒有區別的。oop
那若是是這樣呢?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
這個例子是有關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
的塊級做用域
、暫時性死區
等一系列特性。
這個例子是有關循環的例子。
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]();
複製代碼
咱們能夠看到,babel
將for循環
內的代碼單獨提取出來了,咱們知道閉包
能夠捕獲父級function
的變量,而且咱們也知道對於number
這樣的基本數據類型,JS
在傳參的時候是直接拷貝的,而不是引用。所以對於_loop
這個方法,每次傳過來的i
都會被拷貝一份,而閉包
捕獲的變量僅僅是一個已經被拷貝的變量而已,也便是變量的地址已經改變,不是for
循環中i
的地址了。在沒有let
的時候,要解決這樣的問題,咱們採用的方法每每也是使用閉包
(當即執行函數)來實現。
var tmp = 123;
if (true) {
{
tmp = 'abc';
}
let tmp;
}
複製代碼
通過babel
編譯後的代碼以下:
var tmp = 123;
if (true) {
{
_tmp = 'abc';//ReferenceError
}
var _tmp;
}
複製代碼
運行上面的例子你會獲得一個ReferenceError
的錯誤。這個例子充分說明了let
關鍵字的塊級做用域
的功能,只要是在同級做用域內,全部引用了相同變量名的地方都會被編譯成新的變量名
。
咱們能夠得出一個結論。
let
的塊級做用域的本質就是經過babel重命名變量
。
let a = 10;
var a = 1;
複製代碼
這樣的代碼,你連編譯都沒法編譯,ES6規定在同級做用域內
不容許存在相同的變量名,所以babel
直接在編譯期就報錯了。
const
const
的原理跟let
其實差很少。可是多了一個不可重複賦值。
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
方法來拋出異常。
const
必須在聲明變量的時候就賦值。若是咱們不賦值呢?好比下面
const PI;
複製代碼
你會發現,沒法完成編譯。babel
直接在編譯期就作了檢查。
let
和const
在編譯後仍是以var
來聲明變量,不一樣的地方在於,使用let
或const
聲明的變量,若是在上下文環境中存在相同變量名的var
,那麼會自動將let
聲明的變量名改爲其餘名字,簡單說就是對變量名在編譯期進行重命名
。而正是由於這樣的重命名
的改動,由此引出了不少ES6
對於let
的其餘一些特性,好比:塊級做用域
、暫時性死區
等等。
注意:重命名
不是let
實現的所有