您好,登錄后才能下訂單哦!
當下, 我們幾乎所有的項目都是基于 webpack、rollup 等構建工具進行開發的,模塊化已經是常態。
我們對它并不陌生,今天,我們就再系統的回顧一下ES6的模塊機制, 并總結下常用的操作和最佳實踐, 希望對你有所幫助。
一些簡單的背景
隨用隨取, 是一種我們都希望實現的機制。
在 Javascript 中也一樣,把一個大的 Javascript 程序分割成不同的部分, 哪個部分要被用到,就取那一部分。
在很長一段時間內, NodeJS 擁有這樣的能力, 后來, 越來越多的庫和框架也擁有了模塊化的能力, 比如 CommonJS, 或者基于AMD模型的實現(比如RequireJs),還有后續的Webpack, Babel等。
到2015年,一個標準的模塊化系統誕生了,這就是我們今天要說的主角 - ES6 模型系統。
一眼看上去, 我們不難發現, ES6的模型系統和CommonJS語法非常的相似,畢竟ES6 的模型系統是從CommonJS時代走過來的, 深受CommonJS 影響。
看個簡單的例子,比如在CommonJs中: (https://flaviocopes.com/commonjs/)
//file.js module.exports = value; // 引入value const value = require('file.js')
而在ES6中:
// const.js export const value = 'xxx'; import { value } from 'const.js'
語法是非常相似的。
下面我們就主要看 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 p = document.createElement('p'); p.textContent = text; document.body.appendChild(p); }
如果要處理事件,也是一樣, 看個簡單的例子:
<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/2017/es-modules-in-browsers/
嚴格模式
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
'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 boundexports 的幾種用法
ES6模塊只支持靜態導出,你只可以在模塊的最外層作用域使用export,不可在條件語句中使用,也不能在函數作用域中使用。
從分類上級講, exports 主要有三種:
1、Named Exports (Zero or more exports per module)
2、Default Exports (One per module)
3、Hybrid Exports
exports 總覽:
// Exporting inpidual 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用法。
1. Named 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 }
2. Default exports (導出一個默認 函數/類)
這種方式比較簡單,一般用于一個類文件,或者功能比較單一的函數文件使用。
一個模塊中只能有一個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';
注意這里默認導出不需要用{}。
3. Mixed exports (混合導出)
混合導出,也就是 上面第一點和第二點結合在一起的情況。比較常見的比如 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';
4. Re-exporting (別名導出)
一般情況下,export輸出的變量就是在原文件中定義的名字,但也可以用 as 關鍵字來指定別名,這樣做一般是為了簡化或者語義化export的函數名。
//------ lib.js ------ export function getUserName(){ // ... }; export function setName(){ // ... }; //輸出別名,在import的時候可以同時使用原始函數名和別名 export { getName as get, //允許使用不同名字輸出兩次 getName as getNameV2, setName as set }
5. Module Redirects (中轉模塊導出)
有時候為了避免上層模塊導入太多的模塊,我們可能使用底層模塊作為中轉,直接導出另一個模塊的內容如下:
//------ 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的幾種用法
import的用法和export是一一對應的,但是import支持靜態導入和動態導入兩種方式,動態import支持晚一些,兼容性要差一些。
下面我就總結下import的基本用法:
1. Import All things
當export有多個函數或變量時,如文中export的第一點,可以使用 * as 關鍵字來導出所有函數及變量,同時 as 后面跟著的名稱做為 該模塊的命名空間。
//導出lib的所有函數及變量 import * as lib from 'lib'; //以 lib 做為命名空間進行調用,類似于object的方式 console.log(lib.square(11)); // 121
2. Import a single/multiple export from a module
從模塊文件中導入單個或多個函數,與 * as namepage 方式不同,這個是按需導入。如下例子:
//導入square和 diag 兩個函數 import { square, diag } from 'lib'; // 只導入square 一個函數 import { square } from 'lib'; // 導入默認模塊 import _ from 'lodash'; // 導入默認模塊和單個函數,這樣做主要是簡化單個函數的調用 import _, { each } from 'lodash';
3. Rename multiple exports during import
和 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";
4. Import a module for its side effects only
有時候我們只想import一個模塊進來,比如樣式,或者一個類庫。
// 導入樣式 import './index.less'; // 導入類庫 import 'lodash';
5. Dynamic Imports
靜態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 Modules的詳細內容,更多請關注億速云其它相關文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。