在使用 vue、react、node 的時候,經常會看到 module.exports,export default,require,import等字段,由於我對這些字段的概念很是模糊,因此致使我在寫代碼的時候,在node項目裏混用了 export default,在 vue 的項目裏寫 module.exports。html
那麼今天就來梳理一下有關模塊化的知識。前端
ESM(ECMA Script Modules)模塊主要由兩個命令構成:export 和 import。vue
暴露模塊:export default {} , export {} , export function(){}
引入模塊:import {xxx} from 'path'
複製代碼
import 的大括號裏面指定要從其餘模塊導入的變量名,若是 export 命令沒有寫 default,那麼 import 大括號裏面的變量名,必須與 export 導出的名稱相同。node
// test.js
export {foo}
// main.js
import { foo } from './test.js'
// 若是想爲輸入的變量從新取一個名字,要使用as關鍵字,將輸入的變量重命名
import { foo as bar } from './test.js';
複製代碼
default 爲模塊指定默認輸出,這樣在引入時就沒必要關心模塊輸出的名字。react
// test.js
function foo() {};
export default {foo}
// main.js
import bar from './test.js'
複製代碼
本質上,export default 就是輸出一個叫作 default 的變量或方法,而後系統容許你爲它取任意名字。webpack
有關 ESM 模塊的語法,能夠閱讀 阮一峯 的文章,這裏不詳細寫出全部寫法。git
咱們在使用 import 和 export 的時候,經常看到的是在頂層做用域使用 export 和 import,不會在塊級做用域內看到 import 和 export,這是爲何呢?es6
由於 ESM 模塊的設計思想是儘可能靜態化,編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量,若是處於塊級做用域內,就無法作靜態優化了,違背了 ES6 模塊的設計初衷。github
可是我明明在 vue router 的使用中看到了這樣的寫法:web
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
複製代碼
此時 import 的寫法和常規寫法不同,是import(), 而且確實出如今了塊級做用域內。
ESM有一個提案,建議引入import()函數,完成動態加載。如今支持動態加載的好比:vue router、webpack。
vue router 的 路由懶加載
webpack 動態導入
暴露模塊:module.exports = value 或 exports.xxx = value
引入模塊:require(xxx),若是是第三方模塊,xxx爲模塊名;若是是自定義模塊,xxx爲模塊文件路徑
複製代碼
Node 的模塊輸出和引入的方式與ESM不一樣,Node 採用的是 CommonJS 模塊規範。
CommonJS 規範規定,在每一個模塊內部,module 變量表明當前模塊。這個變量是一個對象,它的 exports 屬性(module.exports)是對外的接口。
Node.js 主要用於服務端項目,CommonJS 規範也主要用於服務端編程,因此 Node 的模塊設計採用 CommonJS 規範很合適。
服務端模塊的加載是同步的,可是瀏覽器資源是異步加載,同步意味着阻塞,在沒有ESM模塊以前,瀏覽器想作模塊化怎麼辦呢?
AMD CMD解決方案。
實際上AMD (Asynchronous Module Definition) 是 RequireJS 在推廣過程當中對模塊定義規範化的產出。
CMD (Common Module Definition) 是 SeaJS 在推廣過程當中對模塊定義的規範化產出。
SeaJS 和 requireJS 解決的都是模塊化問題,只不過在模塊定義方式和模塊加載(能夠說運行、解析)時機上有所不一樣。
如下例子引用自 WEB 前端模塊化都有什麼
AMD
// hello.js
define(function() {
console.log('hello init');
return {
getMessage: function() {
return 'hello';
}
};
});
// world.js
define(function() {
console.log('world init');
});
// main
define(['./hello.js', './world.js'], function(hello) {
return {
sayHello: function() {
console.log(hello.getMessage());
}
};
});
// 輸出
// hello init
// world init
複製代碼
CMD
// hello.js
define(function(require, exports) {
console.log('hello init');
exports.getMessage = function() {
return 'hello';
};
});
// world.js
define(function(require, exports) {
console.log('world init');
exports.getMessage = function() {
return 'world';
};
});
// main
define(function(require) {
var message;
if (true) {
message = require('./hello').getMessage();
} else {
message = require('./world').getMessage();
}
});
// 輸出
// hello init
複製代碼
CMD 的輸出結果中,沒有打印"world init", 並是不 world.js 文件沒有加載。
AMD 與 CMD 都是在頁面初始化時加載完成全部模塊,區別是 CMD 就近依賴, 是當模塊被 require 時纔會觸發執行, AMD 推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊。
一樣都是異步加載模塊,AMD 在加載模塊完成後就會執行該模塊,全部模塊都加載執行完後會進入require的回調函數,執行主邏輯,這樣的效果就是依賴模塊的執行順序和書寫順序不必定一致,看網絡速度,哪一個先下載下來,哪一個先執行,可是主邏輯必定在全部依賴加載完成後才執行。
CMD加載完某個依賴模塊後並不執行,只是下載而已,在全部依賴模塊加載完成後進入主邏輯,遇到require語句的時候才執行對應的模塊,這樣模塊的執行順序和書寫順序是徹底一致的。
這也是不少人說AMD用戶體驗好,由於沒有延遲,依賴模塊提早執行了,CMD性能好,由於只有用戶須要的時候才執行。
AMD & CMD,CommonJS 規範是兩類規範,AMD & CMD 用於瀏覽器模塊化,CommonJS 用於服務端模塊化,可是你們的指望有一個統一的規範來支持這兩種規範。因而,UMD規範誕生了。
UMD (Universal Module Definition),它能夠經過運行時或者編譯時讓同一個代碼模塊在使用 CommonJs、CMD / AMD 的項目中運行,同一個 JavaScript 包在瀏覽器 / 服務端只須要遵照同一個寫法就能夠了。
UMD 沒有本身專有的規範,是集結了 CommonJs、CMD、AMD 的規範於一身,UMD 先判斷是否支持 Node 模塊格式(exports是否存在),存在則使用 Node 模塊格式,再判斷是否支持 AMD(define是否存在),存在則使用 AMD 方式加載模塊,若是前兩個都不存在,則將模塊公開到全局(window或global)。
當即執行函數實現模塊化(IIFE,Immediately-Invoked Function Expression)
使用當即執行函數,表達式中的變量不能從外部訪問。如今項目中已經看不到這樣的寫法了。
例如:
(function(){
var count = 0;
return count;
})();
複製代碼
ESM | CommonJS | AMD | CMD | UMD | |
---|---|---|---|---|---|
加載機制 | 編譯時 | 運行時 | 提早預加載 | 編譯時 & 運行時按需加載 | - |
同步/異步 | 異步 | 同步 | 異步 | 異步,有延遲執行的狀況 | - |
適用場合 | 瀏覽器、服務端 | 服務端 | 瀏覽器 | 瀏覽器 | 瀏覽器、服務端 |
是否常見 | ☆☆☆ | ☆☆☆ | ☆ | ☆ | ☆ |
ESM 在語言標準的層面上,成爲瀏覽器和服務端通用的模塊解決方案。
webpack 在定義模塊上,支持上面提到的全部模塊聲明方式,只須要在 webpack 的 output 中添加 libraryTarget: 'commonjs/amd/umd'便可。