javascript中的模組系統

flydean發表於2021-02-28

簡介

在很久以前,js只是簡單的作為瀏覽器的互動操作而存在,一般都是非常短小的指令碼,所以都是獨立存在的。

但是隨著現代瀏覽器的發展,特別是nodejs的出現,js可以做的事情變得越來越多也越來越複雜。於是我們就需要模組系統來組織不同用途的指令碼,進行邏輯的區分和引用。

今天將會給大家介紹一下js中的模組系統。

CommonJS和Nodejs

CommonJS是由Mozilla公司在2009年1月份提出來了。沒錯,就是那個firfox的公司。

最初的名字叫做ServerJS,在2009年8月的時候為了表示這個標準的通用性,改名為CommonJS。

CommonJS最主要的應用就是服務端的nodejs了。瀏覽器端是不直接支援CommonJS的,如果要在瀏覽器端使用,則需要進行轉換。

CommonJS使用require()來引入模組,使用module.exports來匯出模組。

我們看一個CommonJS的例子:

require("module"); 
require("../file.js"); 
exports.doStuff = function() {}; 
module.exports = someValue;

注意,CommonJS是同步載入的。

AMD非同步模組載入

AMD的全稱是Asynchronous Module Definition 。它提供了一個非同步載入模組的模式。

AMD是RequireJS在推廣過程中對模組定義的規範化產出。

非同步載入的好處就是可以在需要使用模組的時候再進行載入,從而減少了一次性全部載入的時間,尤其是在瀏覽器端,可以提升使用者的體驗。

看下AMD載入模組的定義:

 define(id?, dependencies?, factory);

AMD是通過define來定義和載入依賴模組的。

其中id表示要定義的模組的名字,dependencies表示這個模組的依賴模組,factory是一個函式,用來初始化模組或者物件。

我們看一個例子:

   define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
       exports.verb = function() {
           return beta.verb();
           //Or:
           return require("beta").verb();
       }
   });

這個例子中,我們定義了一個alpha模組,這個模組需要依賴"require", "exports", "beta"三個模組。

並且在factory中匯出了beta模組的verb方法。

define中id和dependencies都不是必須的:

//無id
  define(["alpha"], function (alpha) {
       return {
         verb: function(){
           return alpha.verb() + 2;
         }
       };
   });

//無依賴
   define({
     add: function(x, y){
       return x + y;
     }
   });

甚至我們可以在AMD中使用CommonJS:

   define(function (require, exports, module) {
     var a = require('a'),
         b = require('b');

     exports.action = function () {};
   });

定義完之後,AMD使用require來載入模組:

require([dependencies], function(){});

第一個引數是依賴模組,第二個引數是回撥函式,會在前面的依賴模組都載入完畢之後進行呼叫。載入的模組會以引數形式傳入該函式,從而在回撥函式內部就可以使用這些模組。

require(["module", "../file"], function(module, file) { /* ... */ }); 

require載入模組是非同步載入的,但是後面的回撥函式只會在所有的模組都載入完畢之後才執行。

CMD

CMD是SeaJS在推廣過程中對模組定義的規範化產出。它的全稱是Common Module Definition。

CMD也是使用define來定義模組的,CMD推崇一個檔案作為一個模組:

define(id?, deps?, factory)

看起來和AMD的define很類似,都有id,依賴模組和factory。

這裡的factory是一個函式,帶有三個引數,function(require, exports, module)

我們可以在factory中通過require來載入需要使用的模組,通過exports來匯出對外暴露的模組,module表示的是當前模組。

我們看一個例子:

// 定義模組  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
});

// 載入模組
seajs.use(['myModule.js'], function(my){

});

所以總結下AMD和CMD的區別就是,AMD前置要載入的依賴模組,在定義模組的時候就要宣告其依賴的模組。

而CMD載入完某個依賴模組後並不執行,只是下載而已,只有在用到的時候才使用require進行執行。

ES modules和現代瀏覽器

ES6和現代瀏覽器對模組化的支援是通過import和export來實現的。

首先看下import和export在瀏覽器中支援的情況:

首先我們看下怎麼使用export匯出要暴露的變數或者方法:


export const name = 'square';

export function draw(ctx, length, x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x, y, length, length);

  return {
    length: length,
    x: x,
    y: y,
    color: color
  };
}

基本上,我們可以使用export匯出var, let, const變數或者function甚至class。前提是這些變數或者函式處於top-level。

更簡單的辦法就是將所有要export的放在一行表示:

export { name, draw, reportArea, reportPerimeter };

export實際上有兩種方式,named和default。上面的例子中的export是named格式,因為都有自己的名字。

下面看下怎麼使用export匯出預設的值:

// export feature declared earlier as default
export { myFunction as default };

// export individual features as default
export default function () { ... } 
export default class { .. }

named可以匯出多個物件,而default只可以匯出一個物件。

匯出之後,我們就可以使用import來匯入了:

import { name, draw, reportArea, reportPerimeter } from './modules/square.js';

如果匯出的時候選擇的是default,那麼我們在import的時候可以使用任何名字:

// file test.js
let k; export default k = 12;

// some other file
import m from './test'; // 因為匯出的是default,所以這裡我們可以使用import m來引入
console.log(m);        // will log 12

我們可以在一個module中使用import和export從不同的模組中匯入,然後在同一個模組中匯出,這樣第三方程式只需要匯入這一個模組即可。

export { default as function1,
         function2 } from 'bar.js';

上面的export from 等價於:

import { default as function1,
         function2 } from 'bar.js';
export { function1, function2 };

上面的例子中,我們需要分別import function1 function2才能夠使用,實際上,我們可以使用下面的方式將所有的import作為Module物件的屬性:

import * as Module from './modules/module.js';

Module.function1()
Module.function2()

然後function1,function2就變成了Module的屬性,直接使用即可。

在HTML中使用module和要注意的問題

怎麼在HTML中引入module呢?我們有兩種方式,第一種是使用src選項:

<script type="module" src="main.js"></script>

第二種直接把module的內容放到script標籤中。

<script type="module">
  /* JavaScript module code here */
</script>

注意,兩種script標籤的型別都是module。

在使用script來載入module的時候,預設就是defer的,所以不需要顯示加上defer屬性。

如果你在測試的時候使用file:// 來載入本地檔案的話,因為JS模組安全性的要求,很有可能得到一個CORS錯誤。

最後,import() 還可以作為函式使用,來動態載入模組:

squareBtn.addEventListener('click', () => {
  import('./modules/square.js').then((Module) => {
    let square1 = new Module.Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
    square1.draw();
    square1.reportArea();
    square1.reportPerimeter();
  })
});

本文作者:flydean程式那些事

本文連結:http://www.flydean.com/js-modules/

本文來源:flydean的部落格

歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

相關文章