寫一個適應所有環(huán)境的JS模塊

背景

在ES6以前,JS語言沒有模塊化,如何讓JS不止運行在瀏覽器,且能更有效的管理代碼,于是應運而生CommonJS這種規(guī)范,定義了三個全局變量:

require,exports,module

require 用于引入一個模塊
exports 對外暴露模塊的接口,可以是任何類型
module 是這個模塊本身的對象
require引入時獲取的是這個模塊對外暴露的接口(exports
Node.js 使用了CommonJS規(guī)范:

var foo = require('foo');

var out = foo.bar();

module.exports = out;

在瀏覽器端,不像Node.js內部支持CommonJS,如何進行模塊化,
于是出現了 CMD 與 AMD 兩種方式,其主要代表是 seajs 和 requirejs,
他們都定義了一個全局函數 define 來創(chuàng)建一個模塊:

// CMD
define(function(require, exports, module) {
  var foo = require('foo');
  var out = foo.bar();
  module.exports = out;
})

// AMD
define(['foo'], function(foo) {
  var out = foo.bar();
  return out;
});

可以看出CMD完好的保留了CommonJS的風格,
而AMD用了一種更簡潔的依賴注入和函數返回的方式實現模塊化。
兩者除風格不同外最大區(qū)別在于加載依賴模塊的方式,
CMD是懶加載,在require時才會加載依賴,
而AMD是預加載,在定義模塊時就提前加載好所有依賴。
各有千秋,各有適合的場景,網上有兩者詳細評測和激烈的討論。

正題

我們要實現一個模塊,讓它既能在seajs(CMD)環(huán)境里引入,又能在requirejs(AMD)環(huán)境中引入,
當然也能在Node.js(CommonJS)中使用,另外還可以在沒有模塊化的環(huán)境中用script標簽全局引入,
可謂是對write once,run anywhere的向往,實際上大部分npm的前端組件包也要考慮這個。

  • 首先一個模塊看起來應該是這樣:
var moduleName = {};
return moduleName;

當然,模塊輸出的不止可以是對象,還是可以是任何值,包括一個類。

  • 分析CMD和AMD,我們需要提供一個工廠函數傳入define來定義模塊,所以變成這樣:
function factory () {
  var moduleName = {};
  return moduleName;
}
  • 為適應Node.js,可以來判斷全局變量,由于require在CMD和ADM中都有定義,所以只判斷:
typeof module !== 'undefined' && typeof exports === 'object'

于是變成這樣:

function factory () {
  var moduleName = {};
  return moduleName;
}

if (typeof module !== 'undefined' && typeof exports === 'object') {
  module.exports = factory()
}

至此已經能夠滿足Node.js的需求。

  • 當沒有上述全局變量,且有define全局變量時,我們認為是AMD或CMD,可以直接將factory傳入define:
function factory () {
  var moduleName = {};
  return moduleName;
}

if (typeof module !== 'undefined' && typeof exports === 'object') {
  module.exports = factory()
} else if (typeof define === 'function' && (define.cmd || define.amd)) {
  define(factory)
}

注意:CMD其實也支持return返回模塊接口,所以兩者可以通用。

  • 最后是script標簽全局引入,我們可以將模塊放在window上,

為了模塊內部在瀏覽器和Node.js中都能使用全局對象,我們可以做此判斷:

var global = typeof window !== 'undefined' ? window : global;

同時,我們用一個立刻執(zhí)行的閉包函數將所有代碼包含,來避免污染全局空間,
并將global對象傳入閉包函數,最終變成這樣:

;(function (global) {
  function factory () {
    var moduleName = {};
    return moduleName;
  }
  
  if (typeof module !== 'undefined' && typeof exports === 'object') {
    module.exports = factory()
  } else if (typeof define === 'function' && (define.cmd || define.amd)) {
    define(factory)
  } else {
    global.moduleName = factory();
  }
})(typeof window !== 'undefined' ? window : global);

注意:閉包前加上分號是為了給前一個模塊填坑,分號多了沒問題,少了則語句可能發(fā)生變化。

  • 我們參考一下Vuex的源碼:
;(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
      (global.Vuex = factory());
}(this, (function () {
  'use strict';
  // ……
  var index = {
    Store: Store,
    install: install,
    version: '2.5.0',
    mapState: mapState,
    mapMutations: mapMutations,
    mapGetters: mapGetters,
    mapActions: mapActions,
    createNamespacedHelpers: createNamespacedHelpers
  };
  return index;
})));

這里有兩個變化:
函數factory以匿名函數的方式引入,結構從;()(); 變成 ;(()());;
this代替了typeof window !== 'undefined' ? window : global,this在瀏覽器是window,在Node中是golbal,很是精妙。

  • 稍微簡化一下:
;(function (global) {
  function factory () {
    var index= {};
    return index;
  }
  
  typeof module !== 'undefined' && typeof exports === 'object' ? module.exports = factory() :
  typeof define === 'function' && (define.cmd || define.amd) ? define(factory) :
  (global.moduleName = factory());
})(this);

或者

;(function (flobal, factory) {
  typeof module !== 'undefined' && typeof exports === 'object' ? module.exports = factory() :
  typeof define === 'function' && (define.cmd || define.amd) ? define(factory) :
  (global.moduleName = factory());
}(this, (function () {
    var index = {};
    return index;
  }
)));
  • 于是同一個js文件我們能愉快的在不同環(huán)境這樣引入:
// Node.js
var myModule = require('moduleName');

// Seajs
define(function (require, exports, module) {
  var myModule = require('moduleName');
})

// Requirejs
define(['moduleName'], function (moduleName) {
})

// Browser global
<script src="moduleName.js"></script>

感謝瀏覽,歡迎評論指正,轉載請標明出處。
參考博文:http://www.cnblogs.com/brandonchen/p/5550470.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • JS中的模塊規(guī)范(CommonJS,AMD,CMD),如果你聽過js模塊化這個東西,那么你就應該聽過或Common...
    小蝦米前端閱讀 4,475評論 0 12
  • 什么是模塊化開發(fā)? 前端開發(fā)中,起初只要在script標簽中嵌入幾十上百行代碼就能實現一些基本的交互效果,后來js...
    半世韶華憶闌珊閱讀 721評論 0 0
  • 隨著前端業(yè)務復雜度的增加,模塊化成為一個大的趨勢。而在ES6還未被瀏覽器所支持的情況下,commonjs作為ES6...
    吳高亮閱讀 1,100評論 0 3
  • 1 Node.js模塊的實現# 之前在網上查閱了許多介紹Node.js的文章,可惜對于Node.js的模塊機制大都...
    七寸知架構閱讀 2,159評論 1 50
  • 正式進入了隆冬時節(jié),屋里聽到了凌冽的寒風,想想出門后刺骨的風像刀子一樣刮著臉 ,有想過拒絕出門??墒牵F在不可以像...
    白蘇打閱讀 384評論 4 0

友情鏈接更多精彩內容