ES6+ 模塊化
1. 前言
JavaScript 在設計之初主要用來開發 Web 頁面的交互、動畫和表單驗證等單一的功能,而且程序的體積很小,大多數都是獨立執行的。隨著前端的發展 JavaScript 承接的功能越來越多,Node 的出現讓 JavaScript 可以作為一門后端開發語言,程序的復雜度瞬間提升,所以有必要提供一種將 JavaScript 程序拆分為可按需導入的單獨模塊的機制。Node 是 JavaScript 的先行者,它使用了 CommonJS 的規范來實現模塊化的。在 ES6 沒出來之前有很多模塊化的實踐,比較有名的有:CommonJS、AMD、CMD,每個規范都有自己獨立的思考。
隨著 ES6 模塊的發布,AMD 和 CMD 慢慢地淡出了我們的視野?,F在主要常見的場景是 Node 端還采用 CommonJS 規范,這是歷史原因。前端使用的是 ES6 module 規范,但是不能直接在前端使用,需要通過打包工具進行編譯如:Webpack、Babel、Rollup 等。本文中我們將使用 Webpack 進行模塊化編譯工具,源代碼放在 GitHub 上,僅供參考。
2. 環境搭建
現在的高級瀏覽器還能完全地支持 ES6 的模塊化,如何在瀏覽器中運行 ES6 模塊呢?有兩種方式:
- 在瀏覽器中直接運行 ES6 的模塊化,但是需要做一定的工作,不能像之前直接在本地瀏覽器中打開一個 html 中引入 JS 文件;
- 使用 Webpack、rollup 等模塊化打包工具,html 引入編譯后的 js 文件。
這兩種方式各有優缺點,第一種是原生的使用方式,瀏覽器兼容要求比較高,第二種使用的是第三方打包編譯工具可以很好地解決瀏覽器兼容問題,但是會有一定的學習成本,并且不能直接在瀏覽器中運行,只能使用編譯后的文件。
2.1 瀏覽器運行原生 ES6 模塊
使用瀏覽器運行原生 ES6 模塊的源碼在 ES6-wiki 的 mjs 文件中,瀏覽器是不能直接運行 ES6 模塊化的,需要做一些準備工作。
首先,在引入 js 文件時需要定義 script 的類型:type="module"
。另外,js 文件的后綴不能使用 .js
了,需要使用 .mjs
。這樣還是不能在瀏覽器中運行,還需要最后一步。模塊化會涉及到文件系統,而本地打開的 html 文件是沒有服務的,所以我們要使用 node 服務的方式打開 html 文件,這里我們使用 node 的包 http-server
可以在相應的文件目錄中啟動 node 服務。安裝如下:
npm install --global http-server
安裝完啟動服務的工具還是會有問題,依然打不開,這是需要在瀏覽器中打開一些配置:瀏覽器地址欄輸入:chrome://flags/
然后搜索 JavaScript 把 Experimental JavaScript 項選擇 Enabled 啟用狀態。如下圖。
到這里我們就把前期的工作做完了,如何打開 html 文件呢?在控制臺中進入對應的目錄中執行:http-server 命令。本節的目錄在 ES6-wiki/packages/module/mjs
下。在瀏覽器打開控制臺返回的地址即可,本實例的地址是:http://127.0.0.1:8080/index.html
2.2 使用 Webpack
Webpack 是模塊化打包工具,它兼容現在很多模塊化加載方式,本節課程也主要使用 Webpack 的方式來學習 ES6 的模塊化。Webpack 需要一定的學習成本可以在官網 上學習,這里就不進行介紹了,下面給出 webpack.config.js 的配置如下:
let path = require("path");
let HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html' // 模版文件
})
],
module: {
rules: [
{
test: /\.js$/,
use: {
loader: "babel-loader",
},
},
],
},
};
3. 基本使用
ES6 的模塊化設計思想是盡量靜態化,使得編譯時就能確定模塊的依賴關系,只能在頂級作用域。模塊系統中,每個文件都是一個模塊,模塊之間都是相互獨立。在 ES6 模塊中自動采用 嚴格模式 ,不知道的同學可以去看看,對于學習 JavaScript 語言有一定的幫助。
3.1 export/import
export 是導出語法,import 是導入語法??聪旅娴膶嵗?/p>
// a.js
export let x = 1;
export let y = 2;
// main.js
import {x, y} from './a.js';
console.log(x, y)
上面代碼中,a.js 文件中使用 export 導出 x 和 y 兩個變量,在 mian.js 文件中使用 import 進行導入。a.js 中還可以使用對象的方式導出:
let a = 1;
let b = 2;
export {
a,
b,
}
從上面的 main.js 文件中可以看出,export 使用的是引用方式進行導出的,導出的是一個接口,所以不能直接導出一個值。我們如下實例:
let a = 1;
export a; // 編譯報錯
// 正確的方式如下
Export let a = 1;
雖然使用 export 不能直接導出一個值,但是可以使用 export default
導出一個特定的值:
export default 100;
export 模塊導出的是一個接口,在模塊內如果數據更新,則所依賴的地方的值都是最新的。
// a.js
let a = 1;
setInterval(() => {
a++
})
export {
a
}
// main.js
import { a } from './a.js';
setInterval(() => {
consolog.log(a)
})
import 有聲明的特點,類似 var 的特點,可以實現變量提升,但是不能修改變量對應的值。
// main.js
console.log(a)
import { a } from './a.js';
a = 100; // 這樣賦值是錯誤的
使用 export + from 命令的方式,提供了一種便捷的方式在當前的模塊導出其他模塊的內容,不能在當前模塊下使用導出的變量。
// b.js
let a = 1;
let b = 2;
export {
a,
b,
}
// a.js
export {a,b} from './b.js';
export c = () => {}
// 等價于使用import 先導入,然后再使用 export 導出
import { a, b } from './b';
export {
a,
b,
}
// main.js
import {a, b, c} from './a.js'
export 和 import 命令規定要處于模塊頂層,一旦出現在塊級作用域內,就會報錯。
// a.js
{
export let a = 1;
}
// main.js
{
import { a } from './a';
}
//控制臺答應錯誤內容: 'import' and 'export' may only appear at the top level
上面的代碼中 export 和 import 都放在塊級作用域中的,執行時會報錯,提升你 export 和 import 只能在頂級出現。
3.2 export default 命令
export default 命令用來導出模塊中默認變量或方法,上面我們也提到了使用 export 導出的是一個對象不能導出一個值類型。
// a.js
export default 'imooc';
// main.js
import name from './a.js'
console.log(name); // imooc
export default 命令聲明的函數可以是匿名的。
export default function () {
console.log('imooc');
}
// 等價
function fn() {
console.log('imooc');
}
export default fn;
也可以是一個類:
// a.js
export default class {
constructor() {
console.log('imooc')
}
// ...
}
// main.js
import A from './a';
const a = new A();
console.log(a)
開可以導出的是一個對象:
const obj = {
name: 'imooc',
getLession: function() {
console.log('ES6 imooc');
}
}
export default obj;
3.3 as 命令
as 命令是用來重命名的,在使用 import 命令導入時可以使用 as 來更改變量名。
// a.js
let a = 1;
let b = 2;
export {
a,
b,
}
// main.js
import { a, b as y } from './a';
console.log(a, y); // 1,2
如果模塊中同時有 export default 導出和 export 導出時,在導入時可以使用 as 對默認導出進行重命名。
// a.js
let a = 1;
let b = 2;
export {
a,
b,
}
export default let c = 3;
// main.js
import { a, b, default as c } from './a'
// 等價于下面直接在外面進行使用
import c, { a, b } from './a'
默認導出的內容也可以放在 export 導出的對象中,但是需要使用 as default 命令:
// a.js
let a = 1;
let b = 2;
let c = 'imooc';
export {
a,
b,
c as default, // 相當于 export default 'imooc',給導出的對象增加一個default屬性
}
4. 小結
本節主要講解了 ES6 Module 的使用,通過對 export、import、default、as 命令的講解學習了 ES6 Module 的基本用法,基本上涵蓋了日常使用的場景。