本文是基於 ECMAScript 6 入門 的學習筆記。
只是按照本人理解梳理內容,更加詳細的相關內容請移步 ECMAScript 6 入門 。javascript
es6.ruanyifeng.com/#docs/introhtml
Babel 是一個普遍使用的 ES6 轉碼器,能夠將 ES6 代碼轉爲 ES5 代碼,從而在現有環境執行。這意味着,你能夠用 ES6 的方式編寫程序,又不用擔憂現有環境是否支持。java
在項目目錄下,安裝Babelnode
$ npm install --save-dev @babel/core
複製代碼
Babel 的配置文件是.babelrc
,存放在項目的根目錄下。使用 Babel 的第一步,就是配置這個文件。git
(具體配置看原文)程序員
注意,如下全部 Babel 工具和模塊的使用,都必須先寫好.babelrc
。es6
Babel 提供命令行工具@babel/cli
,用於命令行轉碼。 它的安裝命令以下:github
$ npm install --save-dev @babel/cli
複製代碼
基本用法以下:npm
# 轉碼結果輸出到標準輸出
$ npx babel example.js
# 轉碼結果寫入一個文件
# --out-file 或 -o 參數指定輸出文件
$ npx babel example.js --out-file compiled.js
# 或者
$ npx babel example.js -o compiled.js
# 整個目錄轉碼
# --out-dir 或 -d 參數指定輸出目錄
$ npx babel src --out-dir lib
# 或者
$ npx babel src -d lib
# -s 參數生成source map文件
$ npx babel src -d lib -s
複製代碼
@babel/node
模塊的babel-node
命令,提供一個支持 ES6 的 REPL 環境。它支持 Node 的 REPL 環境的全部功能,並且能夠直接運行 ES6 代碼。編程
@babel/register
模塊改寫require
命令,爲它加上一個鉤子。此後,每當使用require
加載.js
、.jsx
、.es
和`.es6後綴名的文件,就會先用 Babel 進行轉碼。
使用時,必須首先加載@babel/register
。
// index.js
require('@babel/register');
require('./es6.js');
複製代碼
須要注意的是,@babel/register
只會對require
命令加載的文件轉碼,而不會對當前文件轉碼。另外,因爲它是實時轉碼,因此只適合在開發環境使用。
若是某些代碼須要調用 Babel 的 API 進行轉碼,就要使用@babel/core
模塊。
Babel 默認只轉換新的 JavaScript 句法(syntax),而不轉換新的 API,好比Iterator
、Generator
、Set
、Map
、Proxy
、Reflect
、Symbol
、Promise
等全局對象,以及一些定義在全局對象上的方法(好比Object.assign
)都不會轉碼。
舉例來講,ES6 在Array
對象上新增了Array.from
方法。Babel 就不會轉碼這個方法。若是想讓這個方法運行,必須使用babel-polyfill
,爲當前環境提供一個墊片。
Babel 默認不轉碼的 API 很是多,詳細清單能夠查看babel-plugin-transform-runtime
模塊的definitions.js
文件。
Babel 也能夠用於瀏覽器環境,使用@babel/standalone
模塊提供的瀏覽器版本,將其插入網頁。
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// Your ES6 code
</script>
複製代碼
注意,網頁實時將 ES6 代碼轉爲 ES5,對性能會有影響。生產環境須要加載已經轉碼完成的腳本。
Babel 提供一個REPL 在線編譯器,能夠在線將 ES6 代碼轉爲 ES5 代碼。轉換後的代碼,能夠直接做爲 ES5 代碼插入網頁運行。
Google 公司的Traceur轉碼器,也能夠將 ES6 代碼轉爲 ES5 代碼。
Traceur 容許將 ES6 代碼直接插入網頁。首先,必須在網頁頭部加載 Traceur 庫文件。
// 第一個是加載 Traceur 的庫文件
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
// 第二個和第三個是將這個庫文件用於瀏覽器環境
<script src="https://google.github.io/traceur-compiler/bin/BrowserSystem.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
// 第四個則是加載用戶腳本,這個腳本里面可使用 ES6 代碼。
<script type="module">
import './Greeter.js';
</script>
複製代碼
注意,第四個script
標籤的type
屬性的值是module
,而不是text/javascript
。這是 Traceur 編譯器識別 ES6 代碼的標誌,編譯器會自動將全部type=module
的代碼編譯爲 ES5,而後再交給瀏覽器執行.
除了引用外部 ES6 腳本,也能夠直接在網頁中放置 ES6 代碼。 若是想對 Traceur 的行爲有精確控制,能夠採用下面參數配置的寫法。(看原文)
Traceur 也提供一個在線編譯器,能夠在線將 ES6 代碼轉爲 ES5 代碼。轉換後的代碼,能夠直接做爲 ES5 代碼插入網頁運行。
做爲命令行工具使用時,Traceur 是一個 Node 的模塊,首先須要用 npm 安裝。
$ npm install -g traceur
複製代碼
Traceur 直接運行 ES6 腳本文件,會在標準輸出顯示運行結果。如下面的calc.js
爲例。
<script type="module">
class Calc {
constructor() {
console.log('Calc constructor');
}
add(a, b) {
return a + b;
}
}
var c = new Calc();
console.log(c.add(4,5));
</script>
複製代碼
$ traceur calc.js
複製代碼
若是要將 ES6 腳本轉爲 ES5 保存,要採用下面的寫法。
$ traceur --script calc.es6.js --out calc.es5.js --experimental
複製代碼
--script
選項表示指定輸入文件,--out
選項表示指定輸出文件。 爲了防止有些特性編譯不成功,最好加上--experimental
選項。
相關解釋看原文
let
命令聲明的變量,只在let
命令所在的代碼塊內有效。{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
複製代碼
{
console.log(i)
let i=1
}
Uncaught ReferenceError: i is not defined
//在一個塊做用域裏,只要使用let/const命令聲明i,在let/const命令前使用i都會報錯,
//這就是暫時性死區。
複製代碼
var
命令會發生「變量提高」現象,即變量能夠在聲明以前使用,值爲undefined
。這種現象多多少少是有些奇怪的,按照通常的邏輯,變量應該在聲明語句以後纔可使用。
爲了糾正這種現象,let
命令改變了語法行爲,它所聲明的變量必定要在聲明後使用,不然報錯。
// var 的狀況
console.log(foo); // 輸出undefined
var foo = 2;
以上代碼實際爲:
var foo
console.log(foo); // 因此此時foo爲undefined
foo = 2;
// let 的狀況
// 由於 let 沒有變量提高,因此 console 語句時 bar 是不存在的
console.log(bar); // 報錯ReferenceError:bar is not defined
let bar = 2;
複製代碼
只要塊級做用域內存在let
命令,它所聲明的變量就「綁定」(binding)這個區域,再也不受外部的影響。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
複製代碼
上面代碼中,存在全局變量tmp
,可是塊級做用域內let
又聲明瞭一個局部變量tmp
,致使後者綁定這個塊級做用域,因此在let
聲明變量前,對tmp
賦值會報錯。
ES6 明確規定,若是區塊中存在let
和const
命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些變量,就會報錯。
總之,在代碼塊內,使用let
命令聲明變量以前,該變量都是不可用的。這在語法上,稱爲「暫時性死區」(temporal dead zone,簡稱 TDZ)。
if (true) {
// TDZ開始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ結束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
複製代碼
「暫時性死區」也意味着typeof
再也不是一個百分之百安全的操做。
// 1. 直接對一個沒有聲明的變量使用typeof 獲得的是 undefined
typeof undeclared_variable // "undefined"
// 2. 可是若是這個變量是let聲明的,就會報錯。
typeof x; // ReferenceError
let x;
複製代碼
這樣的設計是爲了讓你們養成良好的編程習慣,變量必定要在聲明以後使用,不然就報錯。
有些死區比較隱蔽:
// x的默認值是y,可是此時y未聲明,x=y 就是死區,因此就報錯了。
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 報錯
複製代碼
使用let
聲明變量時,只要變量在尚未聲明完成前使用,就會報錯:
// 不報錯
var x = x;
// 報錯
let x = x;
// ReferenceError: x is not defined
複製代碼
總之,暫時性死區的本質就是,只要一進入當前做用域,所要使用的變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。
let
不容許在相同做用域內,重複聲明同一個變量。
// 報錯
function func() {
let a = 10;
var a = 1;
}
// 報錯
function func() {
let a = 10;
let a = 1;
}
複製代碼
所以,不能在函數內部從新聲明參數。
function func(arg) {
let arg;
}
func() // 報錯
function func(arg) {
{
let arg;
}
}
func() // 不報錯
複製代碼
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
此處 console 語句是想外部使用外部的 tmp ,if內部使用內部的 tmp 。
可是 if 內部的 tmp 泄露到 if 外 ,致使了咱們預期外的結果。
複製代碼
第二種場景,用來計數的循環變量泄露爲全局變量。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
此處 for 結束後,i就應該消失,可是卻還能打印出i,
這會干擾的別的也使用 i 做爲全局變量的地方。
複製代碼
let
實際上爲 JavaScript 新增了塊級做用域。function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
此處 if 內 let聲明的n 就不會干擾到 if 外的 n 。
若是使用的都是var 則最後打印出的是 10 。
複製代碼
// IIFE 寫法
(function () {
var tmp = ...;
...
}());
// 塊級做用域寫法
{
let tmp = ...;
...
}
複製代碼
- 容許在塊級做用域內聲明函數。
- 函數聲明相似於var,即會提高到全局做用域或函數做用域的頭部。
- 同時,函數聲明還會提高到所在的塊級做用域的頭部。
上面三條規則只對 ES6 的瀏覽器實現有效,其餘環境的實現不用遵照,
仍是將塊級做用域的函數聲明看成let處理。
複製代碼
綜上,應該避免在塊級做用域內聲明函數。若是確實須要,也應該寫成函數表達式,而不是函數聲明語句。
// 塊級做用域內部,優先使用函數表達式
{
let a = 'secret';
let f = function () {
return a;
};
}
複製代碼
ES6 的塊級做用域必須有大括號,若是沒有大括號,JavaScript 引擎就認爲不存在塊級做用域。
// 第一種寫法,報錯
if (true) let x = 1;
// 第二種寫法,不報錯
if (true) {
let x = 1;
}
複製代碼
函數聲明也是如此,嚴格模式下,函數只能聲明在當前做用域的頂層。
const
聲明一個只讀的常量。一旦聲明,常量的值就不能改變。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
複製代碼
const
聲明的變量不得改變值,這意味着,const
一旦聲明變量,就必須當即初始化,不能留到之後賦值。
const foo;
// SyntaxError: Missing initializer in const declaration
複製代碼
const
與 let
同樣, 有塊級做用域,暫時性死區,聲明的常量不提高,也不能重複聲明。
ES5 只有兩種聲明變量的方法:var
命令和function
命令。ES6 除了添加let
和const
命令,另外兩種聲明變量的方法:import
命令和class
命令。因此,ES6 一共有 6 種聲明變量的方法。
頂層對象,在瀏覽器環境指的是 window 對象,在 Node 指的是global對象。ES5 之中,頂層對象的屬性與全局變量是等價的。
頂層對象的屬性與全局變量掛鉤,被認爲是 JavaScript 語言最大的設計敗筆之一。這樣的設計帶來了幾個很大的問題,
ES6 爲了改變這一點,且保證兼容性,規定 var
、function
聲明的全局變量依舊是頂層對象的屬性;而let
、const
、class
聲明的全局變量不屬於頂層對象的屬性。
var a = 1;
// 若是在 Node 的 REPL 環境,能夠寫成 global.a
// 或者採用通用方法,寫成 this.a
window.a // 1
let b = 1;
window.b // undefined
複製代碼
JavaScript 語言存在一個頂層對象,它提供全局環境(即全局做用域),全部代碼都是在這個環境中運行。可是,頂層對象在各類實現裏面是不統一的。
同一段代碼爲了可以在各類環境,都能取到頂層對象,如今通常是使用this
變量,可是有侷限性。
this
會返回頂層對象。可是,Node 模塊和 ES6 模塊中,this
返回的是當前模塊。this
,若是函數不是做爲對象的方法運行,而是單純做爲函數運行,this
會指向頂層對象。可是,嚴格模式下,這時this
會返回undefined
。new Function('return this')()
,老是會返回全局對象。可是,若是瀏覽器用了 CSP(Content Security Policy,內容安全策略),那麼eval
、new Function
這些方法均可能沒法使用。如今有一個提案,在語言標準的層面,引入globalThis
做爲頂層對象。也就是說,任何環境下,globalThis
都是存在的,均可以從它拿到頂層對象,指向全局環境下的this
。
墊片庫global-this
模擬了這個提案,能夠在全部環境拿到globalThis
。