JavaScript模組化程式設計規範之CommonJS、AMD、CMD、ES6

2022-03-01 19:00:28
本篇文章給大家帶來了關於中的相關知識,其中主要介紹了模組化程式設計規範,CommonJS、AMD、CMD以及ES6的相關問題,希望對大家有幫助。

相關推薦:

一、前言

AMD、CMD、CommonJsES5中提供的模組化程式設計方案,import/exportES6中新增的模組化程式設計方案。

那麼,究竟什麼什麼是AMD、CMD、CommonJs?他們之間又存在什麼區別呢?專案開發應該選用哪種模組化程式設計規範,又是如何使用?本篇博文將一一解答以上疑問。

二、AMD-非同步模組定義

AMD是」Asynchronous Module Definition」的縮寫,即」非同步模組定義」。它採用非同步方式載入模組,模組的載入不影響它後面語句的執行。

這裡非同步指的是不堵塞瀏覽器其他任務(dom構建,css渲染等),而載入內部是同步的(載入完模組後立即執行回撥)。

RequireJS:是一個AMD框架,可以非同步載入JS檔案,按照模組載入方法,通過define()函數定義,第一個引數是一個陣列,裡面定義一些需要依賴的包,第二個引數是一個回撥函數,通過變數來參照模組裡面的方法,最後通過return來輸出。

AMDRequireJS在推廣過程中對模組定義的規範化產出,它是一個概念,RequireJS是對這個概念的實現,就好比JavaScript語言是對ECMAScript規範的實現。AMD是一個組織,RequireJS是在這個組織下自定義的一套指令碼語言。

不同於CommonJS,它要求兩個引數:

require([module], callback);

第一個引數[module],是一個陣列,裡面的成員是要載入的模組,callback是載入完成後的回撥函數。如果將上述的程式碼改成AMD方式:

require(['math'], function(math) {
  math.add(2, 3);})

其中,回撥函數中引數對應陣列中的成員(模組)。

requireJS載入模組,採用的是AMD規範。也就是說,模組必須按照AMD規定的方式來寫。

具體來說,就是模組書寫必須使用特定的define()函數來定義。如果一個模組不依賴其他模組,那麼可以直接寫在define()函數之中。

define(id, dependencies, factory);
  • id:模組的名字,如果沒有提供該引數,模組的名字應該預設為模組載入器請求的指定指令碼名字;
  • dependencies:模組的依賴,已被模組定義的模組標識的陣列字面量。依賴引數是可選的,如果忽略此引數,它應該預設為 ["require", "exports", "module"]。然而,如果工廠方法的長度屬性小於3,載入器會選擇以函數的長度屬性指定的引數個數呼叫工廠方法。
  • factory:模組的工廠函數,模組初始化要執行的函數或物件。如果為函數,它應該只被執行一次。如果是物件,此物件應該為模組的輸出值。

假定現在有一個math.js檔案,定義了一個math模組。那麼,math.js書寫方式如下:

// math.jsdefine(function() {
  var add = function(x, y) {
    return x + y;
  }

  return  {
    add: add  }})

載入方法如下:

// main.jsrequire(['math'], function(math) {
  alert(math.add(1, 1));})

如果math模組還依賴其他模組,寫法如下:

// math.jsdefine(['dependenceModule'], function(dependenceModule) {
    // ...})

require()函數載入math模組的時候,就會先載入dependenceModule模組。當有多個依賴時,就將所有的依賴都寫在define()函數第一個引數陣列中,所以說AMD是依賴前置的。這不同於CMD規範,它是依賴就近的。
CMD

三、CMD-同步模組定義

CMDCommon Module Definition通用模組定義,是SeaJS在推廣過程中對模組定義的規範化產出,是一個同步模組定義,是SeaJS的一個標準,SeaJSCMD概念的一個實現,SeaJS是淘寶團隊玉伯提供的一個模組開發的js框架。CMD規範是國內發展出來的,就像AMD有個requireJSCMD有個瀏覽器的實現SeaJSSeaJS要解決的問題和requireJS一樣,只不過在模組定義方式和模組載入(可以說執行、解析)時機上有所不同。

CMD 通過define()定義,沒有依賴前置,通過require載入jQuery外掛,CMD是依賴就近,在什麼地方使用到外掛就在什麼地方require該外掛,即用即返,這是一個同步的概念。

CMD 規範中,一個模組就是一個檔案。程式碼的書寫格式如下:

define(function(require, exports, module) {
  // 模組程式碼});

其中,

  • require是可以把其他模組匯入進來的一個引數;
  • exports是可以把模組內的一些屬性和方法匯出的;
  • module 是一個物件,上面儲存了與當前模組相關聯的一些屬性和方法。

AMD是依賴關係前置,在定義模組的時候就要宣告其依賴的模組;
CMD是按需載入依賴就近,只有在用到某個模組的時候再去require,範例程式碼如下:

// CMDdefine(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  // 此處略去 100 行
  var b = require('./b') // 依賴可以就近書寫
  b.doSomething()
  // ... })// AMD 預設推薦的是define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好
  a.doSomething()
  // 此處略去 100 行
  b.doSomething()
  ...})

四、CommonJS 規範

CommonJS規範是通過module.exports定義的,在前端瀏覽器裡面並不支援module.exports,通過node.js後端使用。Nodejs端使用CommonJS規範,前端瀏覽器一般使用AMDCMDES6等定義模組化開發規範。

CommonJS的終極目標是提供一個類似PythonRubyJava的標準庫。這樣的話,開發者可以使用CommonJS API編寫應用程式,然後這些應用就可以執行在不同的JavaScript直譯器和不同的主機環境中。

在相容CommonJS的系統中,你可以使用JavaScript開發以下程式:

  1. 伺服器端JavaScript應用程式;
  2. 命令列工具;
  3. 圖形介面應用程式;
  4. 混合應用程式(如,Titanium或Adobe AIR);

2009年,美國程式設計師Ryan Dahl創造了node.js專案,將javascript語言用於伺服器端程式設計。這標誌"Javascript模組化程式設計"正式誕生。NodeJSCommonJS規範的實現,webpack 也是以CommonJS的形式來書寫。

node.js的模組系統,就是參照CommonJS規範實現的。在CommonJS中,有一個全域性性方法require(),用於載入模組。假定有一個數學模組math.js,就可以像下面這樣載入。

var math = require('math');

然後,就可以呼叫模組提供的方法:

var math = require('math');math.add(2,3); // 5

CommonJS定義的模組分為:模組參照(require)模組定義(exports)模組標識(module)
其中,

  • require()用來引入外部模組;
  • exports物件用於匯出當前模組的方法或變數,唯一的匯出口;
  • module物件就代表模組本身。

雖說NodeJS遵循CommonJS的規範,但是相比也是做了一些取捨,添了一些新東西的。

NPM作為Node包管理器,同樣遵循CommonJS規範。

下面講講commonJS的原理以及簡易實現:

1、原理
瀏覽器不相容CommonJS的根本原因,在於缺少四個Node.js環境變數。

module
exports
require
global

只要能夠提供這四個變數,瀏覽器就能載入 CommonJS 模組。

下面是一個簡單的範例。

var module = {
  exports: {}};(function(module, exports) {
  exports.multiply = function (n) { return n * 1000 };
  }(module, module.exports))var f = module.exports.multiply;
  f(5) // 5000

上面程式碼向一個立即執行函數提供 module 和 exports 兩個外部變數,模組就放在這個立即執行函數裡面。模組的輸出值放在 module.exports 之中,這樣就實現了模組的載入。

2、Browserify 的實現
Browserify 是目前最常用的 CommonJS 格式轉換工具。

請看一個例子,main.js 模組載入 foo.js 模組。

// foo.jsmodule.exports = function(x) {
  console.log(x);};// main.jsvar foo = require("./foo");foo("Hi");

使用下面的命令,就能將main.js轉為瀏覽器可用的格式。

$ browserify main.js > compiled.js

其中,Browserify到底做了什麼?安裝一下browser-unpack,就清楚了。

$ npm install browser-unpack -g

然後,將前面生成的compile.js解包。

$ browser-unpack < compiled.js
[
  {
    "id":1,
    "source":"module.exports = function(x) {\n  console.log(x);\n};",
    "deps":{}
  },
  {
    "id":2,
    "source":"var foo = require(\"./foo\");\nfoo(\"Hi\");",
    "deps":{"./foo":1},
    "entry":true
  }]

可以看到,browerify 將所有模組放入一個陣列,id 屬性是模組的編號,source 屬性是模組的原始碼,deps 屬性是模組的依賴。

因為 main.js 裡面載入了 foo.js,所以 deps 屬性就指定 ./foo 對應1號模組。執行的時候,瀏覽器遇到 require('./foo') 語句,就自動執行1號模組的 source 屬性,並將執行後的 module.exports 屬性值輸出。

五、ES6

有關es6模組特性,強烈推薦阮一峰老師的:ECMAScript 6 入門 - Module 的語法專欄。

要說 ES6 模組特性,那麼就先說說 ES6 模組跟 CommonJS 模組的不同之處。

  • ES6 模組輸出的是值的參照,輸出介面動態繫結,而 CommonJS 輸出的是值的拷貝;
  • ES6 模組編譯時執行,而 CommonJS 模組總是在執行時載入。

CommonJS 模組輸出的是值的拷貝(原始值的拷貝),也就是說,一旦輸出一個值,模組內部的變化就影響不到這個值。

// a.jsvar b = require('./b');console.log(b.foo);setTimeout(() => {
  console.log(b.foo);
  console.log(require('./b').foo);}, 1000);// b.jslet foo = 1;setTimeout(() => {
  foo = 2;}, 500);module.exports = {
  foo: foo,};// 執行:node a.js// 執行結果:// 1// 1// 1

上面程式碼說明,b 模組載入以後,它的內部 foo 變化就影響不到輸出的 exports.foo 了。這是因為 foo 是一個原始型別的值,會被快取。所以如果你想要在 CommonJS 中動態獲取模組中的值,那麼就需要藉助於函數延時執行的特性。

// a.jsvar b = require('./b');console.log(b.foo);setTimeout(() => {
  console.log(b.foo);
  console.log(require('./b').foo);}, 1000);// b.jsmodule.exports.foo = 1;   // 同 exports.foo = 1 setTimeout(() => {
  module.exports.foo = 2;}, 500);// 執行:node a.js// 執行結果:// 1// 2// 2

所以我們可以總結一下:

  • CommonJS 模組重複引入的模組並不會重複執行,再次獲取模組直接獲得暴露的module.exports 物件。
  • 如果你需要處處獲取到模組內的最新值的話,也可以每次更新資料的時候每次都要去更新 module.exports 上的值
  • 如果暴露的 module.exports 的屬性是個物件,那就不存在這個問題了。

相關推薦:

以上就是JavaScript模組化程式設計規範之CommonJS、AMD、CMD、ES6的詳細內容,更多請關注TW511.COM其它相關文章!