一次搞懂 - JavaScript 模塊化詳解
作者:九旬
來源:SegmentFault 思否社區
模塊化的意義
將代碼拆分成獨立的塊,然後再把這些塊使用模塊模式連接起來實現不同的功能。
就像小時候玩的拼圖一樣,不同的拼圖組合在一起就可以拼成任意的形狀。
這種模式的背後思想也很簡單:把邏輯分塊、各自封裝,相互獨立,同時自行決定引入執行那些外部模塊以及暴露自身的那些模塊。
這個基本的思想是所有的 JavaScript 模塊系統的基礎。
文中代碼案例地址:
https://github.com/AnsonZnl/JS-Modules-Sample
模塊化的好處
-
避免命名衝突 (減少命名空間污染)
-
更好的分離, 按需加載
-
更高複用性
-
高可維護性
JS 中常見的模塊
IIFE 模式:匿名函數自調用(閉包)
主要應用在瀏覽器端。
利用閉包的原理創造一個獨有的函數作用域來保存私有變量,達到模塊化的效果。
使用
HTML
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
console.log(myModule.get()); // output-data(獲取內部數據)
myModule.set("new data"); // 設置內部數據
console.log(myModule.data); //output-undefined (不能訪問模塊內部數據)
myModule.data = "xxxx"; //不是修改的模塊內部的data
console.log(myModule.get()); //output-new data 修改後的值
</script>
JS
// module.js文件
(function(window) {
let data = "data";
//獲取數據
function get() {
return data;
}
// 修改數據
function set(val) {
data = val;
}
//暴露行爲
window.myModule = {
get,
set,
};
})(window);
CommonJS
主要應用在服務端,如果在瀏覽器端運行需要藉助其他工具(Browserify)。
暴露模塊: module.exports = value
或者exports.xx = value
(exports 是一個導出的對象)
引入模塊: require(xx)
,如果是第三方模塊,xxx 爲模塊名,如果爲自定義模塊,xxx 爲模塊的文件路徑。
特點
-
所有代碼都運行在模塊作用域,不會污染全局作用域。
-
模塊可以多次加載,但是隻會在第一次加載時運行一次,然後運行結果就被緩存了,以後再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
-
模塊加載的順序,按照其在代碼中出現的順序。
使用
在 Node 中 安裝 uniq 函數。
npm init
npm install uniq --save
// module.js
let arr = [1, 2, 2, 3, 3];
module.exports = {
arr,
};
// app.js
let module1 = require("./module.js");
let uniq = require("uniq");
console.log(uniq(module1.arr)); // [1,2,3]
AMD
全稱是 Asynchronous Module Definition - 異步模塊定義
和 CommonJS 不同的是 AMD 採用非同步的方式來加載模塊。
基本語法
定義暴露模塊
// 定義沒有依賴的模塊
define(function() {
return 模塊;
});
// 定義有依賴的模塊
define(["module1", "module2"], function(m1, m2) {
return 模塊;
});
引入使用模塊
require(["module1", "module2"], function(m1, m2) {
使用m1 和 m2;
});
使用案例
<!-- index.html -->
<body>
<!-- 引入require.js並指定js主文件的入口 -->
<script
data-main="main"
src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.js"
></script>
</body>
// main.js
(function() {
require(["module.js"], function(module) {
let currentUrl = module.getUrl();
alert("當前頁面的URl:" + currentUrl);
});
})();
// module.js
// 定義模塊
define(function() {
let url = window.location.href;
function getUrl() {
return url.toUpperCase();
}
// 暴露模塊
return {
getUrl,
};
});
更多的使用方法請參考:https://requirejs.org/
CMD
CMD--- 是 SeaJS 在推廣過程中對模塊定義的規範化產出,是一個同步模塊定義,是 SeaJS 的一個標準,SeaJS 是 CMD 概念的一個實現,SeaJS 是淘寶團隊提供的一個模塊開發的 JS 框架。
什麼時候用到什麼時候引入,即用即返回,這是一個同步概念。
特點: CMD 是 AMD 在基礎上改進的一種規範,和 AMD 不同在於依賴模塊的執行機制不同,CMD 是就近依賴,而 AMD 是前置依賴。
環境: 瀏覽器環境
語法:
-
導入:define(function(require, exports, module){})
-
導出:define(function(){return '值'})
使用
// main.js
define(function(require, exports, module) {
var moduleA = require("./module.js");
alert(moduleA.a); // 打印出:hello world
});
// module.js
define(function(require, exports, module) {
exports.a = "hello world";
});
<body>
<script
data-main="main"
src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.js"
></script>
</body>
UMD
全稱 Universal Module Definition 看名字就知道,特點是兼容 AMD 和 CommonJS 規範,而且兼容全局引入。
環境: 服務器環境和瀏覽器端
UMD 實現原理很簡單:
-
先判斷是否支持 AMD(define 是否存在),存在則使用 AMD 方式加載模塊;
-
再判斷是否支持 Node.js 模塊格式(exports 是否存在),存在則使用 Node.js 模塊格式;
-
前兩個都不存在,則將模塊公開到全局(window 或 global)
使用
(function(root, factory) {
if (typeof define === "function" && define.amd) {
//AMD
define(["jquery"], factory);
} else if (typeof exports === "object") {
//Node, CommonJS之類的
module.exports = factory(require("jquery"));
} else {
//瀏覽器全局變量(root 即 window)
root.returnExports = factory(root.jQuery);
}
})(this, function($) {
//方法
function myFuncA() {} // 私有方法,因爲沒有返回
function myFuncB() {} // 公共方法,因爲返回了
//暴露公共方法
return {
myFuncB,
};
});
大家平時引入的 jQuery 的 CND 就是 UMD 的,源碼可以查看:
https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js
ES6 Module
在 ES6 之前,模塊化主要是社區在推動進行的,從而出現了 CommonJS 和 AMD 兩個,前者用於服務器後者用於瀏覽器,ES6 模塊的出現將完全替代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的解決方案。
ES6 模塊的設計思想是儘量的靜態化,使得編譯時就能確定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時確定這些東西。比如,CommonJS 模塊就是對象,輸入時必須查找對象屬性。
特點 :
-
按需加載(編譯時加載)
-
import 和 export 命令只能在模塊的頂層,不能在代碼塊之中(如:if 語句中),import() 語句可以在代碼塊中實現異步動態按需動態加載
環境: 服務器環境和瀏覽器端
語法:
-
導入:
import {modules1,modules1,} from '模塊路徑'
-
導出:
export
或者export default
-
動態導入:
import('模塊路徑').then(..)
使用
Node 中 先安裝 Babel:
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node
npm install --save @babel/polyfill
# 然後運行
npx babel-node main.js
// modules/double.js
let mes = "Hello Modules for double";
function sum(value) {
return `${mes} - ${value * 2}`;
}
export default {
mes,
sum,
};
// main.js
import module from "./modules/double";
console.log(module.sum(10)); // Hello Modules for double - 20
瀏覽器中
區別
-
和 CommonJS 的區別:
-
CommonJS 模塊輸出的是一個值得拷貝,ES6 模塊輸出的是值的引用
-
CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口
-
CommonJS 模塊的 require() 是同步加載模塊,ES6 模塊的 import 命令是異步加載,有一個獨立的模塊依賴的解析階段。
缺點
瀏覽器和服務器目前的支持不是很好,現階段使用需要藉助一些工具(Babel)。
-
瀏覽器支持:在新版本的瀏覽器(如 Chrome)中可以使用
<script type="module" src="./foo.js"></script>
寫法 -
服務器支持(Node)有兩種模式,分別是 ES6 模塊和 CommonJS。
-
從 Node.js v13.2 開始,默認支持 ES6 模塊,但是需要採用
.mjs
爲後綴名、或者在package.json
中修改type
字段爲module
(推薦) -
使用 CommonJS 的話需要以
.cjs
爲後綴,也可以設置package.json
中修改type
字段爲commonjs
(推薦)。
最好不要兩者混用。更多的使用方法可以參考:https://es6.ruanyifeng.com/#d...
總結
-
CommonJS 規範主要用於服務端編程,加載模塊是同步的,這並不適合在瀏覽器環境,因爲同步意味着阻塞加載,瀏覽器資源是異步加載的,因此有了 AMD CMD 解決方案。
-
AMD 規範在瀏覽器環境中異步加載模塊,而且可以並行加載多個模塊。不過,AMD 規範開發成本高,代碼的閱讀和書寫比較困難,模塊定義方式的語義不順暢。
-
CMD 規範與 AMD 規範很相似,都用於瀏覽器編程,依賴就近,延遲執行,可以很容易在 Node.js 中運行。不過,依賴 SPM 打包,模塊的加載邏輯偏重
-
ES6 在語言標準的層面上,實現了模塊功能,而且實現得相當簡單,完全可以取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/5yC8Mv4mODLHiXCg2fQzAg