當下, 咱們幾乎全部的項目都是基於 webpack、rollup 等構建工具進行開發的,模塊化已是常態。javascript
咱們對它並不陌生,今天,咱們就再系統的回顧一下ES6的模塊機制, 並總結下經常使用的操做和最佳實踐, 但願對你有所幫助。html
隨用隨取, 是一種咱們都但願實現的機制。前端
在 Javascript 中也同樣,把一個大的 Javascript 程序分割成不一樣的部分, 哪一個部分要被用到,就取那一部分。java
在很長一段時間內, NodeJS 擁有這樣的能力, 後來, 愈來愈多的庫和框架也擁有了模塊化的能力, 好比 CommonJS, 或者基於AMD模型的實現(好比RequireJs),還有後續的Webpack, Babel等。webpack
到2015年,一個標準的模塊化系統誕生了,這就是咱們今天要說的主角 - ES6 模型系統。 es6
一眼看上去, 咱們不發現, ES6的模型系統和CommonJS語法很是的類似,畢竟ES6 的模型系統是從CommonJS時代走過來的, 深受CommonJS 影響。 web
看個簡單的例子,好比在CommonJs中: (https://flaviocopes.com/commo...express
//file.js module.exports = value; // 引入value const value = require('file.js')
而在ES6中:segmentfault
// const.js export const value = 'xxx'; import { value } from 'const.js'
語法是很是類似的。promise
下面咱們就主要看 import 和 export,和幾個相關的特性,瞭解ES6 Modules的更多方面。
模塊化的好處主要是兩點:
1. 避免全局變量污染 2. 有效的處理依賴關係
隨着時代的演進, 瀏覽器原生也開始支持es6 import 和 export 語法了。
先看個簡單的例子:
<script type="module"> import { addTextToBody } from '/util.js'; addTextToBody('Modules are pretty cool.'); </script> // util.js export function addTextToBody(text) { const div = document.createElement('div'); div.textContent = text; document.body.appendChild(div); }
若是要處理事件,也是同樣, 看個簡單的例子:
<button id="test">Show Message</button> <script type="module" crossorigin src="/showImport.js"></script> // showImport.js import { showMessage } from '/show.js' document.getElementById('test').onclick = function() { showMessage(); } // show.js export function showMessage() { alert("Hello World!") }
若是你想跑這個demo, 注意要起個簡單的服務:
$ http-server
不然,你會看到一個CORS拋錯。
至於拋錯的具體緣由和其餘細節,不是本文討論的重點, 感興趣的能夠閱讀以下連接瞭解詳情。
https://jakearchibald.com/201...
https://developer.mozilla.org...
'use strict' 聲明咱們都不陌生, 在es5 時代咱們也常用, 通常是在文件頂部加這個聲明,目的就是禁用Javascript中不太友好的一部分,有助於咱們寫更嚴謹的代碼。
這個特性,在es6語法中是默認開啓的, 若是代碼裏面有不太嚴格的代碼,則會報錯,例如:
下面是我從MDN中摘取的一些在嚴格模式
中被禁用
的部分:
Variables can’t be left undeclared
Function parameters
must have unique names
(or are considered syntax errors)with
is forbiddenread-only properties
Octal numbers
like 00840 are syntax errors
delete undeletable properties
throw an errordelete prop
is a syntax error, instead of assuming delete global[prop]eval
doesn’t introduce new variables into its surrounding scopeeval
and arguments can’t be bound or assigned toarguments
doesn’t magically track changes to method parametersarguments.callee
throws a TypeError, no longer supportedarguments.caller
throws a TypeError, no longer supportedfn.caller
and fn.arguments to access the JavaScript stackReserved words
(e.g protected, static, interface, etc) cannot be boundES6模塊只支持靜態導出,你只能夠在模塊的最外層做用域使用export,不可在條件語句中使用,也不能在函數做用域中使用。
從分類上級講, exports 主要有三種:
// Exporting individual features export let name1, name2, …, nameN; // also var, const export let name1 = …, name2 = …, …, nameN; // also var, const export function functionName(){...} export class ClassName {...} // Export list export { name1, name2, …, nameN }; // Renaming exports export { variable1 as name1, variable2 as name2, …, nameN }; // Exporting destructured assignments with renaming export const { name1, name2: bar } = o; // Default exports export default expression; export default function (…) { … } // also class, function* export default function name1(…) { … } // also class, function* export { name1 as default, … }; // Aggregating modules export * from …; // does not set the default export export * as name1 from …; export { name1, name2, …, nameN } from …; export { import1 as name1, import2 as name2, …, nameN } from …; export { default } from …;
下面我就介紹一下常見的 exports用法。
具名導出,這種方式導出多個函數,通常使用場景好比 utils、tools、common 之類的工具類函數集,或者全站統一變量等。
只須要在變量或函數前面加 export
關鍵字便可。
//------ lib.js ------ export const sqrt = Math.sqrt; export function square(x) { return x * x; } export function diag(x, y) { return sqrt(square(x) + square(y)); } //------ main.js 使用方式1 ------ import { square, diag } from 'lib'; console.log(square(11)); // 121 console.log(diag(4, 3)); // 5 //------ main.js 使用方式2 ------ import * as lib from 'lib'; console.log(lib.square(11)); // 121 console.log(lib.diag(4, 3)); // 5
咱們也能夠直接導出一個列表,例如上面的lib.js能夠改寫成:
//------ lib.js ------ const sqrt = Math.sqrt; function square(x) { return x * x; } function add (x, y) { return x + y; } export { sqrt, square, add }
這種方式比較簡單,通常用於一個類文件,或者功能比較單一的函數文件使用。
一個模塊中只能有一個export default默認輸出。
export default與export的主要區別有兩個:
不須要知道導出的具體變量名, 導入(import)時不須要{}.
//------ myFunc.js ------ export default function () {}; //------ main.js ------ import myFunc from 'myFunc'; myFunc();
導出一個類
//------ MyClass.js ------ class MyClass{} export default MyClass; //------ Main.js ------ import MyClass from 'MyClass';
注意這裏默認導出不須要用{}。
混合導出,也就是 上面第一點和第二點結合在一塊兒的狀況。比較常見的好比 Lodash,都是這種組合方式。
//------ lib.js ------ export var myVar = ...; export let myVar = ...; export const MY_CONST = ...; export function myFunc() { // ... } export function* myGeneratorFunc() { // ... } export default class MyClass { // ... } // ------ main.js ------ import MyClass, { myFunc } from 'lib';
再好比lodash例子:
//------ lodash.js ------ export default function (obj) { // ... }; export function each(obj, iterator, context) { // ... } export { each as forEach }; //------ main.js ------ import _, { forEach } from 'lodash';
通常狀況下,export輸出的變量就是在原文件中定義的名字,但也能夠用 as 關鍵字來指定別名,這樣作通常是爲了簡化或者語義化export的函數名。
//------ lib.js ------ export function getUserName(){ // ... }; export function setName(){ // ... }; //輸出別名,在import的時候能夠同時使用原始函數名和別名 export { getName as get, //容許使用不一樣名字輸出兩次 getName as getNameV2, setName as set }
有時候爲了不上層模塊導入太多的模塊,咱們可能使用底層模塊做爲中轉,直接導出另外一個模塊的內容以下:
//------ myFunc.js ------ export default function() {...}; //------ lib.js ------ export * from 'myFunc'; export function each() {...}; //------ main.js ------ import myFunc, { each } from 'lib'; export 只支持在最外層靜態導出、只支持導出變量、函數、類,以下的幾種用法都是錯誤的。 `錯誤`的export用法: //直接輸出變量的值 export 'Mark'; // 未使用中括號 或 未加default // 當只有一個導出數,需加default,或者使用中括號 var name = 'Mark'; export name; //export不要輸出塊做用域內的變量 function () { var name = 'Mark'; export { name }; }
import的用法和export是一一對應的,可是import支持靜態導入和動態導入兩種方式,動態import支持晚一些,兼容性要差一些。
下面我就總結下import的基本用法:
當export有多個函數或變量時,如文中export的第一點,可使用 * as 關鍵字來導出全部函數及變量,同時 as 後面跟着的名稱作爲 該模塊的命名空間。
//導出lib的全部函數及變量 import * as lib from 'lib'; //以 lib 作爲命名空間進行調用,相似於object的方式 console.log(lib.square(11)); // 121
從模塊文件中導入單個或多個函數,與 * as namepage 方式不一樣,這個是按需導入。以下例子:
//導入square和 diag 兩個函數 import { square, diag } from 'lib'; // 只導入square 一個函數 import { square } from 'lib'; // 導入默認模塊 import _ from 'lodash'; // 導入默認模塊和單個函數,這樣作主要是簡化單個函數的調用 import _, { each } from 'lodash';
和 export 同樣,也能夠用 as 關鍵字來設置別名,當import的兩個類的名字同樣時,可使用 as 來重設導入模塊的名字,也能夠用as 來簡化名稱。
好比:
// 用 as 來 簡化函數名稱 import { reallyReallyLongModuleExportName as shortName, anotherLongModuleName as short } from '/modules/my-module.js'; // 避免重名 import { lib as UserLib} from "alib"; import { lib as GlobalLib } from "blib";
有時候咱們只想import一個模塊進來,好比樣式,或者一個類庫。
// 導入樣式 import './index.less'; // 導入類庫 import 'lodash';
靜態import在首次加載時候會把所有模塊資源都下載下來.
咱們實際開發時候,有時候須要動態import(dynamic import)。
例如點擊某個選項卡,纔去加載某些新的模塊:
// 當動態import時,返回的是一個promise import('lodash') .then((lodash) => { // Do something with lodash. }); // 上面這句實際等同於 const lodash = await import('lodash');
es7的新用法:
async function run() { const myModule = await import('./myModule.js'); const { export1, export2 } = await import('./myModule.js'); const [module1, module2, module3] = await Promise.all([ import('./module1.js'), import('./module2.js'), import('./module3.js'), ]); } run();
以上, 我總結了ES6 Module 的簡單背景和 常見的import , export 用法, 但這遠遠不是它的所有, 篇幅有限,若是想了解更多, 能夠看下面的延伸閱讀部分(質量都還不錯, 能夠看看)。
若是以爲內容有幫助,能夠關注個人公衆號 「 前端e進階 」,掌握最新資訊,一塊兒學習, 一塊兒成長!
ECMAScript 6 modules: the final syntax
Node中沒搞明白require和import,你會被坑的很慘
https://www.zhangxinxu.com/wordpress/2018/08/browser-native-es6-export-import-module/