簡單聊一聊Javascript中的模組化

夏目友人喵發表於2022-03-11

在面試中只要說到模組化的問題,多多少少總會問到這些,umd、amd、cjs、esm,可能聽過其中一個兩個,或者都聽說過。接下來我們先簡單瞭解一下他們到底是什麼,又有什麼樣的區別呢。

        最開始的時候,Javascript是沒有匯入匯出模組的這種方法,這就有一個比較頭疼的問題,就是我們所有的程式碼都要寫在一個檔案裡面,那可真的是又臭又長。有問題了,排查起來還特別的麻煩,定義個變數還總是出各種問題。後來,為了解決這些煩人的問題,各路大佬就推出了umd、amd、cjs、esm、cmd。     

模組化的優點


  • 實現程式碼的可複用,具有獨立的作用域,避免全域性變數被汙染 

  • 便於程式碼編寫和維護,提高開發效率

  • 實現按需載入,例如:npm就是一個巨大的模組化倉庫

 

CommonJS(cjs)


CommonJS主要用於服務端,nodejs是預設使用cjs模組規範。每一個檔案就是一個模組,有自己獨立的作用域、變了、方法等,對其他的模組都不可見。模組可以多次載入,但是隻在第一次時執行,然後執行結果就被快取,後續載入則直接讀取快取結果。若想再次執行模組,則需要清楚快取。模組的載入順序是按照在程式碼中的出現的順序進行載入的。
如果想要多次執行一個模組,可以匯出一個方法,然後直接呼叫方法即可。特點


  1. 在執行模組程式碼之前,NodeJs會使用函式封裝器將其封裝為閉包形式的方法

  2. 可以通過 module.exports 匯出模組內容,但是不能對exports有賦值操作

  3. CJS 是同步匯入模組,通過 require(id) 引入模組、JSON、或本地檔案

  4. 被引入的模組將被快取到require.cache 物件中,如果刪除該物件的某個模組會導致下次require的時候重新載入該模組

  5. CJS 不能在瀏覽器中工作,必須經過轉換和打包。node.js 就是使用 commonJs 的模組規範,可以在 js 檔案中直接使用

 // 定義模組 sum.js    
const num = 0;
function add(a, b) { return a + b; }
module.exports = {
//在這裡寫上需要向外暴露的函式、變數
  add: add,
  num: num
}
/** 必須加./路徑,不加的話只會去node_modules檔案找 **/
// 引用自定義的模組時,引數包含路徑,可省略.js
const sum = require('./sum');
sum.add(2, 5);
// 引用核心模組時,不需要帶路徑
var http = require('http');
http.createService(...).listen(3000);

amd(Asynchronous Module Definition)


        amd是Javascript的非同步模組化定義方案,專門為前端而做的,代表庫為:require.js。

  1. amd是非同步匯入

  2. 專為前端而設計

  3. 語法不如cjs直觀

define(['dep1', 'dep2'], function (dep1, dep2) {   
  //Define the module value by returning a value.
  return function () {};
});
// "simplified CommonJS wrapping" https://requirejs.org/docs/whyamd.html
define(function (require) {
  var dep1 = require('dep1'),
  dep2 = require('dep2');
  return function () {};
});

        RequireJS 是一個JavaScript模組載入器(檔案和模組載入工具),使用RequireJS載入模組化指令碼將提高程式碼的載入速度和質量它針對瀏覽器使用場景進行了優化,並且也可以應用到其他 JavaScript 環境中,例如 Node.js。

  1. 引入require.js時,我們會通過data-main引入入口檔案;

  2. require.js獲取到入口檔案後,將檔案以及對應的依賴通過script標籤append到html上;

  3. 依賴是依次、同步append到html,但是script標籤的載入卻是非同步的;

  4. 依賴載入完成後,會立即呼叫其回撥執行函式;

  5. 入口檔案監聽到所有的依賴都載入完成後,再呼叫其回撥函式(即回撥函式factory)

<!-- project.html -->
<!DOCTYPE html>
<html>
<head>
<title>My Sample Project</title>
<script data-main="scripts/main" src="scripts/require.js"></script>
</head>
<body>
<h1>My Sample Project</h1>
</body>
</html>
// main.js requirejs(["helper/util"], function(util) {
// you can do everything you want
});

cmd(Common Module Definition)


        cmd通用模組定義,amd的優化版,代表庫:sea.js

  1. AMD是依賴前置,提前載入依賴;CMD依賴後置,使用時才載入

  2. 所有程式碼都執行在模組作用域中,不會汙染全域性變數;

  3. 模組會被非同步載入;

  4. 模組載入完成後,不會執行其回撥函式,而是等到主函式執行且需要的執行依賴的時候才執行依賴函式(依賴後置、按需載入)

(對於seajs想了解的同學可以自己去官網瞭解,因為小妖也不太熟悉)

 

umd(Universal Module Definition)


        一般可以把cjs轉為UMD,用於瀏覽器執行時使用。是一種通用的寫法,是在amd和cjs兩個流行而不統一的規範情況下,才催生出umd來統一規範的,umd前後端均可通用,UMD 更像是一種配置多個模組系統的模式,當使用 Rollup/Webpack 之類的打包器時,UMD 通常用作備用模組。

// CommonJS側重伺服器,而AMD側重於瀏覽器,兩者的模組不能共享/*  UMD的思想很簡單   判斷是AMD則使用AMD方式  是commonJS則使用CommonJS方式  都不是則將模組公開給全域性(window或global) */ (function (root, factory) {   if (typeof define === "function" && define.amd) {       // amd方式       define(["jquery", "underscore"], factory);   } else if (typeof exports === "object") {       // cjs方式       module.exports = factory(require("jquery"), require("underscore"));   } else {       // 公開暴露給全域性       root.Requester = factory(root.$, root._);   }}(this, function ($, _) {    // 屬性    var property = Math.property;    // 方法    function a() { };                   // 私有方法,因為它沒被返回    function b() { return a() };        // 公共方法,因為被返回了    function c(x, y) { return x + y };  // 公共方法,因為被返回了    // 暴露公共方法    return {        ip: PI,        b: b,        c: c    }}));

esm(ES Module)


          ESM 代表 ES 模組,es6原生支援的。這是 Javascript 提出的實現一個標準模組系統的方案。

  1. 大多現代瀏覽器都支援

  2. 非同步載入和簡單語法

  3. 因為是靜態module結構,支援打包工具去除無用程式碼

// 在html中呼叫
<script type="module">
import {func1} from 'my-lib';
func1();
</script>

// 靜態匯入:匯入本地的檔案、庫或者遠端模組
import { createStore } from "https://unpkg.com/redux@4.0.5/es/redux.mjs";
import * as myModule from './util.js';
// 動態匯入:ES模組實際上是JavaScript物件:我們可以解構它們的屬性以及呼叫它們的任何公開方法
btn.addEventListener(
"click", () => {
  // loads named export
  import("./util.js")
  .then(({ funcA }) => {
    funcA(); });
});

const loadUtil = () => import("./util.js");
// 返回的是一個 promise。所以也可以使用可以使用 `async/await` btn.addEventListener(
"click", () => {
  loadUtil().then(module => {
    module.funcA();
    module.default();
})})
// 使用 async/await 的寫法
btn.addEventListener("click", async () => {
  const utilsModule = await loadUtil();
  utilsModule.funcA();
  utilsModule.default();
})

 

最後

  • 由於 esm 具有簡單的語法,非同步特性和可搖樹性,因此它是最好的模組化方案

  • umd 隨處可見,通常在 esm 不起作用的情況下用作備用

  • cjs 是同步的,適合後端

  • amd 是非同步的,適合前端(cmd是amd的優化)

最最後:歡迎關注大家我的公眾號

 

相關文章