一次搞懂-JavaScript 模塊化詳解

作者:九旬
來源:SegmentFault 思否社區(qū)
模塊化的意義
將代碼拆分成獨(dú)立的塊,然后再把這些塊使用模塊模式連接起來實(shí)現(xiàn)不同的功能。
就像小時(shí)候玩的拼圖一樣,不同的拼圖組合在一起就可以拼成任意的形狀。
這種模式的背后思想也很簡單:把邏輯分塊、各自封裝,相互獨(dú)立,同時(shí)自行決定引入執(zhí)行那些外部模塊以及暴露自身的那些模塊。
這個(gè)基本的思想是所有的 JavaScript 模塊系統(tǒng)的基礎(chǔ)。
文中代碼案例地址:
https://github.com/AnsonZnl/JS-Modules-Sample
模塊化的好處
避免命名沖突(減少命名空間污染) 更好的分離, 按需加載 更高復(fù)用性 高可維護(hù)性
JS 中常見的模塊
IIFE 模式:匿名函數(shù)自調(diào)用(閉包)
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
console.log(myModule.get()); // output-data(獲取內(nèi)部數(shù)據(jù))
myModule.set("new data"); // 設(shè)置內(nèi)部數(shù)據(jù)
console.log(myModule.data); //output-undefined (不能訪問模塊內(nèi)部數(shù)據(jù))
myModule.data = "xxxx"; //不是修改的模塊內(nèi)部的data
console.log(myModule.get()); //output-new data 修改后的值
</script>
// module.js文件
(function(window) {
let data = "data";
//獲取數(shù)據(jù)
function get() {
return data;
}
// 修改數(shù)據(jù)
function set(val) {
data = val;
}
//暴露行為
window.myModule = {
get,
set,
};
})(window);
CommonJS
module.exports = value或者exports.xx = value(exports 是一個(gè)導(dǎo)出的對象)require(xx),如果是第三方模塊,xxx 為模塊名,如果為自定義模塊,xxx 為模塊的文件路徑。所有代碼都運(yùn)行在模塊作用域,不會(huì)污染全局作用域。 模塊可以多次加載,但是只會(huì)在第一次加載時(shí)運(yùn)行一次,然后運(yùn)行結(jié)果就被緩存了,以后再加載,就直接讀取緩存結(jié)果。要想讓模塊再次運(yùn)行,必須清除緩存。 模塊加載的順序,按照其在代碼中出現(xiàn)的順序。
在 Node 中 安裝 uniq 函數(shù)。
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
// 定義沒有依賴的模塊
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("當(dāng)前頁面的URl:" + currentUrl);
});
})();
// module.js
// 定義模塊
define(function() {
let url = window.location.href;
function getUrl() {
return url.toUpperCase();
}
// 暴露模塊
return {
getUrl,
};
});
CMD
導(dǎo)入:define(function(require, exports, module){}) 導(dǎo)出: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
先判斷是否支持 AMD(define 是否存在),存在則使用 AMD 方式加載模塊; 再判斷是否支持 Node.js 模塊格式(exports 是否存在),存在則使用 Node.js 模塊格式; 前兩個(gè)都不存在,則將模塊公開到全局(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() {} // 私有方法,因?yàn)闆]有返回
function myFuncB() {} // 公共方法,因?yàn)榉祷亓?br>
//暴露公共方法
return {
myFuncB,
};
});
ES6 Module
按需加載(編譯時(shí)加載) import 和 export 命令只能在模塊的頂層,不能在代碼塊之中(如:if 語句中),import()語句可以在代碼塊中實(shí)現(xiàn)異步動(dòng)態(tài)按需動(dòng)態(tài)加載
導(dǎo)入: import {modules1,modules1,} from '模塊路徑'導(dǎo)出: export或者export default動(dòng)態(tài)導(dǎo)入: import('模塊路徑').then(..)
<!-- 揺樹(tree-shaking) -->
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node
npm install --save @babel/polyfill
# 然后運(yùn)行
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 的區(qū)別: CommonJS 模塊輸出的是一個(gè)值得拷貝,ES6 模塊輸出的是值的引用 CommonJS 模塊是運(yùn)行時(shí)加載,ES6 模塊是編譯時(shí)輸出接口 CommonJS 模塊的 require()是同步加載模塊,ES6 模塊的 import 命令是異步加載,有一個(gè)獨(dú)立的模塊依賴的解析階段。
瀏覽器和服務(wù)器目前的支持不是很好,現(xiàn)階段使用需要借助一些工具(Babel)。
瀏覽器支持:在新版本的瀏覽器(如 Chrome)中可以使用 <script type="module" src="./foo.js"></script>寫法服務(wù)器支持(Node)有兩種模式,分別是 ES6 模塊和 CommonJS。 從 Node.js v13.2 開始,默認(rèn)支持 ES6 模塊,但是需要采用 .mjs為后綴名、或者在package.json中修改type字段為module(推薦)使用 CommonJS 的話需要以 .cjs為后綴,也可以設(shè)置package.json中修改type字段為commonjs(推薦)。
總結(jié)
CommonJS 規(guī)范主要用于服務(wù)端編程,加載模塊是同步的,這并不適合在瀏覽器環(huán)境,因?yàn)橥揭馕吨枞虞d,瀏覽器資源是異步加載的,因此有了 AMD CMD 解決方案。 AMD 規(guī)范在瀏覽器環(huán)境中異步加載模塊,而且可以并行加載多個(gè)模塊。不過,AMD 規(guī)范開發(fā)成本高,代碼的閱讀和書寫比較困難,模塊定義方式的語義不順暢。 CMD 規(guī)范與 AMD 規(guī)范很相似,都用于瀏覽器編程,依賴就近,延遲執(zhí)行,可以很容易在 Node.js 中運(yùn)行。不過,依賴 SPM 打包,模塊的加載邏輯偏重 ES6 在語言標(biāo)準(zhǔn)的層面上,實(shí)現(xiàn)了模塊功能,而且實(shí)現(xiàn)得相當(dāng)簡單,完全可以取代 CommonJS 和 AMD 規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案。

評論
圖片
表情
