Proxy 這個詞的原意是代理,用在這裏表示由它來「代理」某些操做,能夠譯爲「代理器」,即用本身的定義覆蓋了語言的原始定義。ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。node
var proxy = new Proxy(target, handler);
上面代碼中的new Proxy()表示生成一個Proxy實例,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲。es6
var a={}; var proxy = new Proxy(a, { get: function(target, property) { return 35; } }); proxy.tt="1"; proxy.tt //35 a.tt //"1"
上面代碼中,對於proxy來講,與a對象除了get方法不同,其餘所有同樣。且對proxy 的操做就至關於對a進行操做。express
要使得Proxy起做用,必須針對Proxy實例(上例是proxy對象)進行操做,而不是針對目標對象(上例是空對象)進行操做。json
若是handler沒有設置任何攔截,那就等同於直接通向原對象。數組
var target = {}; var handler = {}; var proxy = new Proxy(target, handler); proxy.a = 'b'; target.a // "b"
上面代碼中,handler是一個空對象,沒有任何攔截效果,訪問proxy就等同於訪問target。瀏覽器
對於能夠設置、但沒有設置攔截的操做,則直接落在目標對象上,按照原先的方式產生結果。緩存
若是目標對象是函數,那麼還有兩種額外操做能夠攔截。app
Proxy.revocable方法返回一個可取消的 Proxy 實例。異步
let target = {}; let handler = {}; let {proxy, revoke} = Proxy.revocable(target, handler); proxy.foo = 123; proxy.foo // 123 revoke(); proxy.foo // TypeError: Revoked
Proxy.revocable方法返回一個對象,該對象的proxy屬性是Proxy實例,revoke屬性是一個函數,能夠取消Proxy實例。上面代碼中,當執行revoke函數以後,再訪問Proxy實例,就會拋出一個錯誤。async
Proxy.revocable的一個使用場景是,目標對象不容許直接訪問,必須經過代理訪問,一旦訪問結束,就收回代理權,不容許再次訪問。
雖然 Proxy 能夠代理針對目標對象的訪問,但它不是目標對象的透明代理,即不作任何攔截的狀況下,也沒法保證與目標對象的行爲一致。主要緣由就是在 Proxy 代理的狀況下,目標對象內部的this關鍵字會指向 Proxy 代理。
const target = { m: function () { console.log(this === proxy); } }; const handler = {}; const proxy = new Proxy(target, handler); target.m() // false proxy.m() // true
上面代碼中,一旦proxy代理,後者內部的this就是指向proxy,而不是target。
此外,有些原生對象的內部屬性,只有經過正確的this才能拿到,因此 Proxy 也沒法代理這些原生對象的屬性。
const target = new Date(); const handler = {}; const proxy = new Proxy(target, handler); proxy.getDate(); // TypeError: this is not a Date object.
上面代碼中,getDate方法只能在Date對象實例上面拿到,若是this不是Date對象實例就會報錯。這時,this綁定原始對象,就能夠解決這個問題。
const target = new Date('2015-01-01'); const handler = { get(target, prop) { if (prop === 'getDate') { return target.getDate.bind(target); } return Reflect.get(target, prop); } }; const proxy = new Proxy(target, handler); proxy.getDate() //1
Reflect對象的設計目的有如下幾個:
Reflect對象一共有13個靜態方法。
修飾器(Decorator)是一個函數,用來修改類的行爲。這是 ES 的一個提案,目前 Babel 轉碼器已經支持。
下面的@decorator就是一個修飾器。
@decorator class A {} // 等同於 class A {} A = decorator(A) || A; @testable class MyTestableClass { // ... } function testable(target) { target.isTestable = true; } MyTestableClass.isTestable // true //@testable就是一個修飾器。它修改了MyTestableClass這個類的行爲,爲它加上了靜態屬性isTestable。target指會被修飾的類。
注意,修飾器對類的行爲的改變,是代碼編譯時發生的,而不是在運行時。這意味着,修飾器能在編譯階段運行代碼。也就是說,修飾器本質就是編譯時執行的函數。
修飾器函數的第一個參數,就是所要修飾的目標類。
function testable(target) { // ... }
若是以爲一個參數不夠用,能夠在修飾器外面再封裝一層函數。
function testable(isTestable) { return function(target) { target.isTestable = isTestable; } } @testable(true) class MyTestableClass {} MyTestableClass.isTestable // true @testable(false) class MyClass {} MyClass.isTestable // false
修飾器也能夠修飾類的屬性。
class Person { @readonly name() { return `${this.first} ${this.last}` } } //修飾器readonly用來修飾「類」的name方法。
修飾器函數修飾類的屬性時一共能夠接受三個參數,第一個參數是所要修飾的目標對象,第二個參數是所要修飾的屬性名,第三個參數是該屬性的描述對象。
若是同一個方法有多個修飾器,會像剝洋蔥同樣,先從外到內進入,而後由內向外執行。
function dec(id){ console.log('evaluated', id); return (target, property, descriptor) => console.log('executed', id); } class Example { @dec(1) @dec(2) method(){} } // evaluated 1 // evaluated 2 // executed 2 // executed 1
上面代碼中,外層修飾器@dec(1)先進入,可是內層修飾器@dec(2)先執行。
修飾器只能用於類和類的方法,不能用於函數,由於存在函數提高,而類是不會提高的。
core-decorators.js是一個第三方模塊,提供了幾個常見的修飾器。
在修飾器的基礎上,能夠實現Mixin模式。所謂Mixin模式,就是在一個對象之中混入另一個對象的方法。
//部署一個通用腳本mixins.js,將mixin寫成一個修飾器。 export function mixins(...list) { return function (target) { Object.assign(target.prototype, ...list); }; } //經過mixins這個修飾器,實現了在MyClass類上面「混入」Foo對象的foo方法。 import { mixins } from './mixins'; const Foo = { foo() { console.log('foo') } }; @mixins(Foo) class MyClass {} let obj = new MyClass(); obj.foo() // "foo"
ES6 模塊經過export命令顯式指定輸出的代碼,再經過import命令輸入。 ES6 模塊是編譯時加載,使得靜態分析成爲可能。
// ES6模塊 import { stat, exists, readFile } from 'fs';
上面代碼的實質是從fs模塊加載3個方法,其餘方法不加載。這種加載稱爲「編譯時加載」或者靜態加載,即 ES6 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。
ES6 的模塊自動採用嚴格模式,無論你有沒有在模塊頭部加上"use strict";。
嚴格模式主要有如下限制:
ES6 模塊之中,頂層的this指向undefined,即不該該在頂層代碼使用this。
模塊功能主要由兩個命令構成:export和import。export命令用於規定模塊的對外接口,import命令用於輸入其餘模塊提供的功能。
一個模塊就是一個獨立的文件。
該文件內部的全部變量,外部沒法獲取。若是你但願外部可以讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量。
export的正確寫法:
// 寫法一 export var m = 1; // 寫法二 var m = 1; export {m};//在export命令後面,使用大括號指定所要輸出的一組變量。應優先使用該寫法。 // 寫法三 var n = 1; export {n as m};
export命令除了輸出變量,還能夠輸出函數或類(class)。一般狀況下,export輸出的變量就是原本的名字,可是可使用as關鍵字重命名。
export function f() {}; function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion }; //代碼使用as關鍵字,重命名了函數v1和v2的對外接口。重命名後,v2能夠用不一樣的名字輸出兩次。
export語句輸出的接口,與其對應的值是動態綁定關係,即經過該接口,能夠取到模塊內部實時的值。
export var foo = 'bar'; setTimeout(() => foo = 'baz', 500);
上面代碼輸出變量foo,值爲bar,500毫秒以後變成baz。這一點與 CommonJS 規範徹底不一樣。CommonJS 模塊輸出的是值的緩存,不存在動態更新。
export命令能夠出如今模塊的任何位置,只要處於模塊頂層就能夠。若是處於塊級做用域內,就會報錯,import命令也是如此。
這是由於處於條件代碼塊之中,就無法作靜態優化。
function foo() { export default 'bar' // SyntaxError } foo() //export語句放在函數之中,結果報錯。
使用export命令定義了模塊的對外接口之後,其餘 JS 文件就能夠經過import命令加載這個模塊。
// main.js import {firstName, lastName, year} from './profile'; function setName(element) { element.textContent = firstName + ' ' + lastName; }
上面代碼的import命令,用於加載profile.js文件,並從中輸入變量。import命令接受一對大括號,裏面指定要從其餘模塊導入的變量名。大括號裏面的變量名,必須與被導入模塊(profile.js)對外接口的名稱相同。
若是想爲輸入的變量從新取一個名字,import命令要使用as關鍵字,將輸入的變量重命名。
import { lastName as surname } from './profile';
import後面的from指定模塊文件的位置,能夠是相對路徑,也能夠是絕對路徑,.js路徑能夠省略。若是隻是模塊名,不帶有路徑,那麼必須有配置文件,告訴 JavaScript 引擎該模塊的位置。
import {myMethod} from 'util'; //util是模塊文件名,因爲不帶有路徑,必須經過配置,告訴引擎怎麼取到這個模塊。
import命令具備提高效果,會提高到整個模塊的頭部,首先執行。
這是由於import命令是編譯階段執行的,在代碼運行以前。
foo(); import { foo } from 'my_module'; //該代碼不會報錯,由於import的執行早於foo的調用。
因爲import是靜態執行,因此不能使用表達式和變量這些只有在運行時才能獲得結果的語法結構。
// 報錯 import { 'f' + 'oo' } from 'my_module'; // 報錯 let module = 'my_module'; import { foo } from module; // 報錯 if (x === 1) { import { foo } from 'module1'; } else { import { foo } from 'module2'; }
上面三種寫法都會報錯,由於它們用到了表達式、變量和if結構。在靜態分析階段,這些語法都是無法獲得值的。
import語句會執行所加載的模塊。
若是屢次重複執行同一句import語句,那麼只會執行一次,而不會執行屢次。
import 'lodash'; import 'lodash';
上面代碼加載了兩次lodash,僅執行一次lodash,且不輸入任何值。
//foo和bar在兩個語句中加載,可是它們對應的是同一個my_module實例。 import { foo } from 'my_module'; import { bar } from 'my_module'; // 等同於 import { foo, bar } from 'my_module';
模塊總體加載即用星號(*)指定一個對象,全部輸出值都加載在這個對象上面。
下面是一個circle.js文件,它輸出兩個方法area和circumference。
// circle.js export function area(radius) { return Math.PI * radius * radius; } export function circumference(radius) { return 2 * Math.PI * radius; }
如今,加載這個模塊。
import * as circle from './circle'; console.log('圓面積:' + circle.area(4)); console.log('圓周長:' + circle.circumference(14));
模塊總體加載所在的那個對象(上例是circle)不容許運行時改變。因此下面的寫法都是不容許的。
import * as circle from './circle'; // 下面兩行都是不容許的 circle.foo = 'hello'; circle.area = function () {};
export default命令,爲模塊指定默認輸出。其餘模塊加載該模塊時,import命令能夠爲該輸出指定任意名字。export default後面不須要使用大括號,且export default命令只能使用一次。
// export-default.js export default function () { console.log('foo'); } // import-default.js import customName from './export-default'; //這時的import命令後面,不使用大括號。 customName(); // 'foo'
export default命令用在非匿名函數前,也是能夠的。
// export-default.js export default function foo() { console.log('foo'); } // 或者寫成 function foo() { console.log('foo'); } export default foo;//export default後面沒有使用大括號。
上面代碼中,foo函數的函數名foo,在模塊外部是無效的。加載的時候,視同匿名函數加載。
默認輸出和正常輸出的比較:使用export default時,對應的import語句不須要使用大括號,名字可任意;不使用export default時,對應的import語句須要使用大括號,名字須與被導入模塊對外接口的名稱相同。
// 第一組 export default function crc32() { // 輸出 // ... } import crc32 from 'crc32'; // 輸入 // 第二組 export function crc32() { // 輸出 // ... }; import {crc32} from 'crc32'; // 輸入
本質上,export default命令是將該命令後面的值,賦給default變量之後再輸出
,即輸出一個叫作default的變量或方法,而後系統容許你爲它取任意名字。因此,下面的寫法是有效的。
// modules.js function add(x, y) { return x * y; } export {add as default}; // 等同於 // export default add; // app.js import { default as xxx } from 'modules'; // 等同於 // import xxx from 'modules';
export default命令後面不能跟變量聲明語句,由於它只是輸出一個叫作default的變量。
// 正確 export var a = 1; // 正確 var a = 1; export default a; // 錯誤 export default var a = 1; // 正確 export default 42; //正確是由於指定外對接口爲default。 // 報錯 export 42;//報錯是由於沒有指定對外的接口。
若是想在一條import語句中,同時輸入默認方法和其餘變量,能夠寫成下面這樣。
export default function (obj) { // ··· } export function each(obj, iterator, context) { // ··· } export { each as forEach };//該行語句的意思是暴露出forEach接口,默認指向each接口,即forEach和each指向同一個方法。 import _, { each } from 'lodash';
export default也能夠用來輸出類。
// MyClass.js export default class { ... } // main.js import MyClass from 'MyClass'; let o = new MyClass();
若是在一個模塊之中,先輸入後輸出同一個模塊,import語句能夠與export語句寫在一塊兒。
export { foo, bar } from 'my_module'; // 等同於 import { foo, bar } from 'my_module'; export { foo, bar };
默認接口的寫法以下。
export { default } from 'foo';
模塊之間也能夠繼承。
假設有一個circleplus模塊,繼承了circle模塊。
// circleplus.js export * from 'circle'; export var e = 2.71828182846; export default function(x) { return Math.exp(x); }
上面代碼中的export ,表示再輸出circle模塊的全部屬性和方法。注意,export 命令會忽略circle模塊的default方法。而後,上面代碼又輸出了自定義的e變量和默認方法。
const聲明的常量只在當前代碼塊有效。若是想設置跨模塊的常量(即跨多個文件),或者說一個值要被多個模塊共享,能夠採用下面的寫法。
// constants.js 模塊 export const A = 1; export const B = 3; export const C = 4; // test1.js 模塊 import * as constants from './constants'; console.log(constants.A); // 1 console.log(constants.B); // 3 // test2.js 模塊 import {A, B} from './constants'; console.log(A); // 1 console.log(B); // 3
import命令會被 JavaScript 引擎靜態分析,先於模塊內的其餘模塊執行,屬於編譯時加載。有一個提案,建議引入import()函數,完成動態加載,即運行時加載模塊。
import(specifier)
上面代碼中,import函數的參數specifier,指定所要加載的模塊的位置。import命令可以接受什麼參數,import()函數就能接受什麼參數,二者區別主要是後者爲動態加載。
import()返回一個 Promise 對象。import()加載模塊成功之後,這個模塊會做爲一個對象,看成then方法的參數。下面是一個例子。
const main = document.querySelector('main'); import(`./section-modules/${someVariable}.js`) .then(module => { module.loadPageInto(main); }) .catch(err => { main.textContent = err.message; });
import()函數能夠用在任何地方,不只僅是模塊,非模塊的腳本也可使用。它是運行時執行,也就是說,何時運行到這一句,就會加載指定的模塊。import()相似於 Node 的require方法,區別主要是前者是異步加載,後者是同步加載。
動態的模塊路徑:import()容許模塊路徑動態生成。
import(f()) .then(...); //代碼中,根據函數f的返回結果,加載不一樣的模塊。
import()加載模塊成功之後,這個模塊會做爲一個對象,看成then方法的參數。所以,可使用對象解構賦值的語法,獲取輸出接口。若是模塊有default輸出接口,能夠用參數直接得到。
import('./myModule.js') .then(myModule => { console.log(myModule.default); });
若是想同時加載多個模塊,能夠採用下面的寫法。
Promise.all([ import('./module1.js'), import('./module2.js'), import('./module3.js'), ]) .then(([module1, module2, module3]) => { ··· });
import()也能夠用在 async 函數之中。
瀏覽器容許腳本異步加載,下面就是兩種異步加載的語法。
<script src="path/to/myModule.js" defer></script> <script src="path/to/myModule.js" async></script>
defer與async的區別是:前者要等到整個頁面正常渲染結束,纔會執行;後者一旦下載完,渲染引擎就會中斷渲染,執行這個腳本之後,再繼續渲染。一句話,defer是「渲染完再執行」,async是「下載完就執行」。另外,若是有多個defer腳本,會按照它們在頁面出現的順序加載,而多個async腳本是不能保證加載順序的。
瀏覽器加載 ES6 模塊,也使用<script>標籤,可是要加入type="module"屬性。
<script type="module" src="foo.js"></script>
上面代碼在網頁中插入一個模塊foo.js,因爲type屬性設爲module,因此瀏覽器知道這是一個 ES6 模塊。
瀏覽器對於帶有type="module"的<script>,都是異步加載,不會形成堵塞瀏覽器,即等到整個頁面渲染完,再執行模塊腳本
,等同於打開了<script>標籤的defer屬性。
<script type="module" src="foo.js"></script> <!-- 等同於 --> <script type="module" src="foo.js" defer></script>
<script>標籤的async屬性也能夠打開,這時只要加載完成,渲染引擎就會中斷渲染當即執行。執行完成後,再恢復渲染。
<script type="module" src="foo.js" async></script>
ES6 模塊也容許內嵌在網頁中,語法行爲與加載外部腳本徹底一致。
<script type="module"> import utils from "./utils.js"; // other code </script>
對於外部的模塊腳本(上例是foo.js),有幾點須要注意:
有兩個重大差別:
第二個差別是由於 CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完纔會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。
CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。ES6 模塊的運行機制與 CommonJS 不同。JS 引擎對腳本靜態分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裏面去取值。所以,ES6 模塊不會緩存運行結果,而是動態地去被加載的模塊取值。
ES6 輸入的模塊變量是隻讀的,對它進行從新賦值會報錯。
// lib.js export let obj = {}; // main.js import { obj } from './lib'; obj.prop = 123; // OK obj = {}; // TypeError
上面代碼中,main.js從lib.js輸入變量obj,能夠對obj添加屬性,可是從新賦值就會報錯。由於變量obj指向的地址是隻讀的,不能從新賦值。
export經過接口,輸出的是同一個值。不一樣的腳本加載這個接口,獲得的都是一樣的實例。
Node 對 ES6 模塊的處理比較麻煩,由於它有本身的 CommonJS 模塊格式,與 ES6 模塊格式是不兼容的。目前的解決方案是,將二者分開,ES6 模塊和 CommonJS 採用各自的加載方案。
在靜態分析階段,一個模塊腳本只要有一行import或export語句,Node 就會認爲該腳本爲 ES6 模塊,不然就爲 CommonJS 模塊。若是不輸出任何接口,可是但願被 Node 認爲是 ES6 模塊,能夠在腳本中加一行語句。
export {};//不輸出任何接口的 ES6 標準寫法。
若是不指定絕對路徑,Node 加載 ES6 模塊會依次尋找如下腳本,與require()的規則一致。
import './foo'; // 依次尋找 // ./foo.js // ./foo/package.json // ./foo/index.js import 'baz'; // 依次尋找 // ./node_modules/baz.js // ./node_modules/baz/package.json // ./node_modules/baz/index.js // 尋找上一級目錄 // ../node_modules/baz.js // ../node_modules/baz/package.json // ../node_modules/baz/index.js // 再上一級目錄
ES6 模塊之中,頂層的this指向undefined;CommonJS 模塊的頂層this指向當前模塊,這是二者的一個重大差別。
Node 採用 CommonJS 模塊格式,模塊的輸出都定義在module.exports這個屬性上面。在 Node 環境中,使用import命令加載 CommonJS 模塊,Node 會自動將module.exports屬性,看成模塊的默認輸出,即等同於export default。
CommonJS 模塊的輸出緩存機制,在 ES6 加載方式下依然有效。
因爲 ES6 模塊是編譯時肯定輸出接口,CommonJS 模塊是運行時肯定輸出接口,因此採用import命令加載 CommonJS 模塊時,不容許採用下面的寫法。
import {readfile} from 'fs';
上面的寫法不正確,由於fs是 CommonJS 格式,只有在運行時才能肯定readfile接口,而import命令要求編譯時就肯定這個接口。解決方法就是改成總體輸入。
import * as express from 'express'; const app = express.default(); import express from 'express'; const app = express();
採用require命令加載 ES6 模塊時,ES6 模塊的全部輸出接口,會成爲輸入對象的屬性。
// es.js let foo = {bar:'my-default'}; export default foo; foo = null; // cjs.js const es_namespace = require('./es'); console.log(es_namespace.default); // {bar:'my-default'}
上面代碼中,default接口變成了es_namespace.default屬性。另外,因爲存在緩存機制,es.js對foo的從新賦值沒有在模塊外部反映出來。
「循環加載」(circular dependency)指的是,a腳本的執行依賴b腳本,而b腳本的執行又依賴a腳本。
一般,「循環加載」表示存在強耦合,若是處理很差,還可能致使遞歸加載,使得程序沒法執行,所以應該避免出現。
CommonJS的一個模塊,就是一個腳本文件。require命令第一次加載該腳本,就會執行整個腳本,而後在內存生成一個對象。之後須要用到這個模塊的時候,就會到exports屬性上面取值。即便再次執行require命令,也不會再次執行該模塊,而是到緩存之中取值。也就是說,CommonJS模塊不管加載多少次,都只會在第一次加載時運行一次,之後再加載,就返回第一次運行的結果,除非手動清除系統緩存。
{ id: '...', exports: { ... }, loaded: true, ... }
上面代碼就是Node內部加載模塊後生成的一個對象。該對象的id屬性是模塊名,exports屬性是模塊輸出的各個接口,loaded屬性是一個布爾值,表示該模塊的腳本是否執行完畢。其餘還有不少屬性。
CommonJS模塊的重要特性是加載時執行,即腳本代碼在require的時候,就會所有執行。一旦出現某個模塊被"循環加載",就只輸出已經執行的部分,還未執行的部分不會輸出。例如:
//腳本文件a.js代碼 exports.done = false; var b = require('./b.js'); console.log('在 a.js 之中,b.done = %j', b.done); exports.done = true; console.log('a.js 執行完畢');
上面代碼之中,a.js腳本先輸出一個done變量,而後加載另外一個腳本文件b.js。注意,此時a.js代碼就停在這裏,等待b.js執行完畢,再往下執行。
//腳本文件b.js的代碼 exports.done = false; var a = require('./a.js'); console.log('在 b.js 之中,a.done = %j', a.done); exports.done = true; console.log('b.js 執行完畢');
上面代碼之中,b.js執行到第二行,就會去加載a.js,這時,就發生了「循環加載」。系統會去a.js模塊對應對象的exports屬性取值,但是由於a.js尚未執行完,從exports屬性只能取回已經執行的部分,而不是最後的值。
a.js已經執行的部分,只有一行。
exports.done = false;
所以,對於b.js來講,它從a.js只輸入一個變量done,值爲false。
而後,b.js接着往下執行,等到所有執行完畢,再把執行權交還給a.js。因而,a.js接着往下執行,直到執行完畢。
ES6模塊是動態引用,若是使用import從一個模塊加載變量(即import foo from 'foo'),那些變量不會被緩存,而是成爲一個指向被加載模塊的引用,須要開發者本身保證,真正取值的時候可以取到值。
// a.js以下 import {bar} from './b.js'; console.log('a.js'); console.log(bar); export let foo = 'foo'; // b.js import {foo} from './a.js'; console.log('b.js'); console.log(foo); export let bar = 'bar'; //運行結果 b.js undefined a.js bar
上面代碼中,因爲a.js的第一行是加載b.js,因此先執行的是b.js。而b.js的第一行又是加載a.js,這時因爲a.js已經開始執行了,因此不會重複執行,而是繼續往下執行b.js,因此第一行輸出的是b.js。
接着,b.js要打印變量foo,這時a.js還沒執行完,取不到foo的值,致使打印出來是undefined。b.js執行完,開始執行a.js,這時就一切正常了。
瀏覽器目前還不支持ES6模塊,爲了如今就能使用,能夠將轉爲ES5的寫法。除了Babel能夠用來轉碼以外,還有如下兩個方法,也能夠用來轉碼。
參考自:ECMAScript 6 入門