學習編程語言是一件鍥而不捨的事情,從學會簡單的語法就能寫出程序,到理解類型和設計模式,再到考慮代碼的組織架構。誰不是從這樣一點點深刻和積累的呢?入門老是輕鬆又使人愉悅的,但隨着知識點愈來愈多學習的曲線卻驟然陡峭。但隨着對語言的深刻理解,再回頭來從新審閱基本的知識,又會有柳暗花明又一村的豁然感,「啊,原來是這樣的」那種感受。javascript
這個 「3分鐘系列」 將利用 babel 編譯工具,來學習分析 es6+ 的部分特性。經過編譯後的 es5 代碼,咱們能夠從中瞭解到 es6+ 特性的實現細節,更好的掌握新特性的適用性。java
本文大量使用了阮一峯「 ECMAScript 6 入門」和「你不知道的 JavaScript」書中的代碼。git
cutting linees6
// @babel/core: 7.2.2
// @babel/preset-env: 7.2.3
// .babelrc
{
"presets": [
["@babel/preset-env", {
"ignoreBrowserslistConfig": true
}]
]
}
複製代碼
cutting linegithub
ES5 只有全局做用域和函數做用域,沒有塊級做用域,帶來了不少不合理的場景:web
for
循環中的計數變量會泄露爲全局變量。在 ES5 中爲了建立一個塊級做用域,除了普通的函數聲明外,就是當即執行函數表達式了(IIFE)。編程
// es5
var a = 2;
(function IIFE () {
var a = 3;
console.log(a); // 3
})();
console.log(a); // 2
複製代碼
cutting linejson
ES6 中引入了塊級做用域,當在花括號中存在 let
或者 const
時,花括號內爲塊級做用域:設計模式
let
或 const
所聲明的變量。let
或 const
所聲明的變量名能夠和外層相同。// ------ 源碼區 ------
var x = 1;
let y = 1;
if (true) {
var x = 2;
let y = 2;
}
console.log(x); // 2
console.log(y); // 1
// ------ 編譯區 ------
"use strict";
var x = 1;
var y = 1;
if (true) {
var x = 2;
var _y = 2;
}
console.log(x); // 2
console.log(y); // 1
複製代碼
特性:因爲 let
使花括號提高爲塊級做用域,使得即便聲明瞭相同的變量名 y
也互不干擾。瀏覽器
Babel:爲了實現此效果,Babel 重命名了塊級做用域內 let
聲明的變量名。
cutting line
// ------ 源碼區 ------
let a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6]();
// ------ 編譯區 ------
"use strict";
var a = [];
var _loop = function _loop(i) {
a[i] = function () {
console.log(i);
};
};
for (var i = 0; i < 10; i++) {
_loop(i);
}
a[6]();
複製代碼
特性:當用 var
聲明變量 i
時,a[6]()
輸出的是 10
,由於循環體沒有緩存變量 i
。
Babel:當用 let
聲明時,Babel 建立了一個閉包 _loop
來緩存變量。
cutting line
cutting line
// ------ 源碼區 ------
let i = 1;
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// ------ 編譯區 ------
"use strict";
var i = 1;
for (var _i = 0; _i < 3; _i++) {
var _i2 = 'abc';
console.log(_i2);
}
複製代碼
特性:for
循環中,初始化變量的部分和循環體內分別是兩個做用域。
Babel:Babel 重命名了循環體內的變量 i
。
cutting line
ES6 新增了 let
命令,用來聲明變量。
let
擁有以下特性:
// ------ 源碼區 ------
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
// ------ 編譯區 ------
"use strict";
{
var _a = 10;
var b = 1;
}
a; // ReferenceError: a is not defined.
b; // 1
複製代碼
特性:let
所聲明的變量只會在其做用域內有效,做用域外調用該變量會報錯。
Babel:爲了用 ES5 實現相同的特性,Babel 重命名了 let
聲明的變量名,使得做用域內外的變量名不一樣。
cutting line
// ------ 源碼區 ------
// var 的狀況
console.log(foo); // 輸出undefined
var foo = 2;
// let 的狀況
console.log(bar); // 報錯ReferenceError
let bar = 2;
// ------ 編譯區 ------
"use strict";
// var 的狀況
console.log(foo); // 輸出undefined
var foo = 2; // let 的狀況
console.log(bar); // 報錯ReferenceError
var bar = 2;
複製代碼
特性:let
必須先聲明再使用,這種語法行爲糾正了 var
變量提高的現象。
Babel:Babel 在此處並無作特殊的處理。
重要提示:
let
在編譯後沒有添加異常提示,Babel 在變量提高細節上處理不佳,你的代碼運行結果可能會和你預想中的有差別。養成良好的代碼習慣,有助於避免此坑。
cutting line
縮寫爲「TDZ」(temporal dead zone)。
當區塊中存在 let
或 const
命令,這個區塊對這些命令聲明的變量就造成了封閉區域,凡是在聲明前就使用這些變量,就會報錯。
// ------ 源碼區 ------
var tmp = 123;
{
tmp = 'abc'; // ReferenceError
let tmp;
}
// ------ 編譯區 ------
"use strict";
var tmp = 123;
{
_tmp = 'abc'; // ReferenceError
var _tmp;
}
複製代碼
特性:上面的源碼區中,指望給外部的 tmp
賦值 abc
。但因爲在區塊中聲明瞭同名變量,因此此時 tmp
變量被內部佔用。
Babel:Babel 很好的處理了這個特性,將區塊內的 tmp
變量改名爲 _tmp
以區分。可是,仍然會存在變量提高的問題。
cutting line
// ------ 源碼區 ------
function foo() {
let a = 10;
let a = 1;
}
// ------ 編譯區 ------
// 編譯報錯 Duplicate declaration "a"
複製代碼
特性:let
不容許在相同做用域內,重複聲明同一個變量。
Babel:當重複聲明同一個變量時,編譯沒法經過。
cutting line
const
聲明一個只讀的常量。
const
有以下特性:
// ------ 源碼區 ------
const a = 1;
// ------ 編譯區 ------
var a = 1;
複製代碼
特性:最普通的使用方式。
Babel:若是上下文沒有違背規範,則會直接用 var
來聲明。
cutting line
// ------ 源碼區 ------
const a = 0;
a = 1;
// ------ 編譯區 ------
"use strict";
function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); }
var a = 0;
a = (_readOnlyError("a"), 1);
複製代碼
特性:一旦 const
聲明瞭一個變量後嘗試再次賦值,會報異常。
Babel:Babel 檢測到變量被在此賦值,主動插入了一個報錯,並終止程序運行。
cutting line
// ------ 源碼區 ------
const a;
// ------ 編譯區 ------
// 編譯報錯 Unexpected token
複製代碼
cutting line
Babel 在處理 let
和 const
的大部分特性時都不錯,可是在 變量先聲明後使用 的細節上處理不佳。須要咱們保持良好的變量聲明習慣。
cutting line
目前 86% 左右的瀏覽器都原生支持 let
和 const
。
cutting line
當我文章寫到此處,我仍然疑惑爲何 Babel 爲何會沒有正確編譯暫時性死區的特性,留下這樣的問題。
直到我找到了 @babel/plugin-transform-block-scoping
插件。
原來想要 Babel 編譯時正確實現該特性,須要引入這個插件並開啓配置。
// .babelrc
{
"presets": [
["@babel/preset-env", {
"ignoreBrowserslistConfig": true
}]
],
"plugins": [
["@babel/plugin-transform-block-scoping", {
"tdz": true
}]
]
}
複製代碼
cutting line
此時 Babel 會爲代碼插入一個異常。
// ------ 源碼區 ------
i;
let i = 1;
// ------ 編譯區 ------
"use strict";
(function () {
throw new ReferenceError("i is not defined - temporal dead zone");
})();
var i = 1;
複製代碼
可是文檔中也說了,這個插件沒有覆蓋全部邊界狀況,也應該當心使用。
Temporal Dead Zone · Issue #826 · babel/website · GitHub
@babel/plugin-transform-block-scoping · Babel
cutting line