回顧:前端模塊化和AMD、CMD規范(全)


先列舉下一些著名言論:

“我想定義一個 each 方法遍歷對象,但頁頭的 util.js 里已經定義了一個,我的只能叫 eachObject 了,好無奈。”

“RequireJS 是沒有明顯的 bug,SeaJS 是明顯沒有 bug。”

“在用SeaJS,除了打包非常痛苦外,其他的還好”

“你變了精彩的魔術,我們會為你喝彩。但你想讓我們信任你,你得主動解釋魔術的奧秘。否則我會覺得自己被耍了。”

“這兩個加載器和標准沒有優劣之分,只有差別。具體還是要根據實際情況進行選擇;”

……

前端模塊化

這個是個老掉牙的話題,我就不再誤人子弟了,歡迎閱讀這篇由 玉伯 大大執筆的《 前端模塊化開發的價值

模塊化能解決:

  • 模塊的版本管理 。通過別名等配置,配合構建工具,可以比較輕松地實現模塊的版本管理。

  • 提高可維護性 。模塊化可以讓每個文件的職責單一,非常有利於代碼的維護。Sea.js 還提供了 nocache、debug 等插件,擁有在線調試等功能,能比較明顯地提升效率。

  • 前端性能優化 。Sea.js 通過異步加載模塊,這對頁面性能非常有益。Sea.js 還提供了 combo、flush 等插件,配合服務端,可以很好地對頁面性能進行調優。

  • 跨環境共享模塊 。CMD 模塊定義規范與 Node.js 的模塊規范非常相近。通過 Sea.js 的 Node.js 版本,可以很方便實現模塊的跨服務器和瀏覽器共享。

sea.js 舉例

sea.js是一個專門為解決瀏覽器模塊化開發的方案,SeaJS定義了一個全局函數 define 它用來定義一個模塊,SeaJS提倡:文件即模塊。

  • 先貼一段模塊代碼

所有模塊都通過 define 來定義

define(function(require, exports, module) {

	// 通過 require 引入依賴 注意 .js 可以省略
	var $ = require('jquery');
	// 你也可以引入自己的函數依賴
	var Spinning = require('./yourFunction');
	var util = {};
	util.sayHello = function(){
		return 'seajs向你問好';
	}
	// 通過 exports 對外提供接口
	// exports 和 module.exports 區別見下文:
	//exports.doSomething = function() {

	//};
	// 或者通過 module.exports 提供整個接口
	module.exports = util;

});
// 看完這一段 你應該明白了 require, exports, module 三個參數

前端模塊化開發的歷史

既然是 回顧 那就應該好文不斷: 《 前端模塊化開發那點歷史

AMD規范及其代表: RequireJS

AMD規范

  • 中文版在這里AMD (中文版),或者: 鳥語原版

  • 簡單說: 就是 異步模塊定義(Asynchronous Module Definition),它是 依賴前置 (因為依賴必須一開始就寫好)會先 盡早地執行(依賴)模塊 , 相當於所有的require都被提前了,它的 require 分全局和局部, 一個API當多個用

  • define() 函數

看下它的具體參數說明

define(
    //這個模塊的名稱
    "types/Manager",
    //依賴的模塊
    ["types/Employee"],
    //函數執行時,所有依賴項加載。這個函數的參數依賴上述模塊。
    function (Employee) {
        function Manager () {
            this.reports = [];
        }
        //開始執行
        Manager.prototype = new Employee();
        //返回經理構造函數可以由其他模塊的應用。
        return Manager;
    }
);

一些例子

創建一個名為"alpha"的模塊,使用了require,exports,和名為"beta"的模塊:

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

一個返回對象的匿名模塊:

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

一個使用了簡單CommonJS轉換的模塊定義:

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

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

CMD規范及其代表: SeaJS

CMD規范

  • 中文版在這里CMD 模塊定義規范 ,也有 鳥語版

  • 簡單說 : CMD(Common Module Definition)更貼近 CommonJS Modules/1.1 和 Node Modules 規范,一個模塊就是一個文件;它推崇 依賴就近 想什么時候 require 就什么時候加載,實現了 懶加載, 延遲執行 (as lazy as possible) ;也沒有全局 require, 每個API都簡單純粹

  • define() 函數類似,看定義即可

module.exports 和 exports 區別

經常使用的 API 只有 define, require, require.async, exports, module.exports 這五個。其他 API 有個印象就好。

傳給 factory 構造方法的 exports 參數是 module.exports 對象的一個引用。只通過 exports 參數來提供接口,有時無法滿足開發者的所有需求。 比如當模塊的接口是某個類的實例時,需要通過 module.exports 來實現:

define(function(require, exports, module) {
	// exports 是 module.exports 的一個引用
	console.log(module.exports === exports); // true
	// 重新給 module.exports 賦值
	module.exports = new SomeClass();
	// exports 不再等於 module.exports
	console.log(module.exports === exports); // false
});

對 module.exports 的賦值需要同步執行,不能放在回調函數里。下面這樣是不行的:

// x.js
define(function(require, exports, module) {
	// 錯誤用法
	setTimeout(function() {
		module.exports = { a: "hello" };
	}, 0);

});

在 y.js 里有調用到上面的 x.js:

// y.js
define(function(require, exports, module) {

	var x = require('./x');

	// 無法立刻得到模塊 x 的屬性 a
	console.log(x.a); // undefined

});

AMD(RequireJS)和CMD(SeaJS)異同

看看執行流程

當我們看到RequireJS 的接口,

require(['a','b'],function(){
	//Do something
})

實際做的事情是:
1、require 函數檢查依賴的模塊,根據配置文件,獲取js文件的實際路徑
2、根據js文件實際路徑,在dom中插入script節點,並綁定onload事件來獲取該模塊加載完成的通知。
3、依賴script全部加載完成后,調用回調函數

Sea.js在調用時

define('a',function(require,exports,modules){
	var b = require('b')
})

1、通過回調函數的Function.toString函數,使用正則表達式來捕捉內部的require字段,找到require('jquery')內部依賴的模塊jquery
2、根據配置文件,找到jquery的js文件的實際路徑
3、在dom中插入script標簽,載入模塊指定的js,綁定加載完成的事件,使得加載完成后將js文件綁定到require模塊指定的id(這里就是jquery這個字符串)上
4、回調函數內部依賴的js全部加載(暫不調用)完后,調用回調函數
5、當回調函數調用require('jquery'),即執行綁定在'jquery'這個id上的js文件,即刻執行,並將返回值傳給var b

注意: SeaJS 這種用 ** 正則表達式 捕捉require內部依賴模塊的方式** ,使得無法利用尚未執行的回調函數中的js運行環境,導致require函數的內部只能將依賴的模塊名稱硬編碼,就不能寫下面這樣的代碼了

define('a',function(require,exports,modules){
	//錯誤
    var b = require('Us'+'er');
})

而只能寫成

define('a',function(require,exports,modules){
	//正確
    var b = require('User');
})

其他的區別其實介紹時已經說明了,上文~ 小總結:玉伯:與 RequireJS 的異同

相同之處

RequireJS 和 Sea.js 都是模塊加載器,倡導模塊化開發理念,核心價值是讓 JavaScript 的模塊化開發變得簡單自然。

不同之處

兩者的主要區別如下:

  • 定位有差異。RequireJS 想成為瀏覽器端的模塊加載器,同時也想成為 Rhino / Node 等環境的模塊加載器。Sea.js 則專注於 Web 瀏覽器端,同時通過 Node 擴展的方式可以很方便跑在 Node 環境中。

  • 遵循的規范不同。RequireJS 遵循 AMD(異步模塊定義)規范,Sea.js 遵循 CMD (通用模塊定義)規范。規范的不同,導致了兩者 API 不同。Sea.js 更貼近 CommonJS Modules/1.1 和 Node Modules 規范。

  • 推廣理念有差異。RequireJS 在嘗試讓第三方類庫修改自身來支持 RequireJS,目前只有少數社區采納。Sea.js 不強推,采用自主封裝的方式來“海納百川”,目前已有較成熟的封裝策略。

  • 對開發調試的支持有差異。Sea.js 非常關注代碼的開發調試,有 nocache、debug 等用於調試的插件。RequireJS 無這方面的明顯支持。

  • 兩者代碼質量有差異。RequireJS 是沒有明顯的 bug,SeaJS 是明顯沒有 bug。

  • 插件機制不同。RequireJS 采取的是在源碼中預留接口的形式,插件類型比較單一。Sea.js 采取的是通用事件機制,插件類型更豐富。

還有不少差異,涉及具體使用方式和源碼實現,歡迎有興趣者研究並發表看法。

總之,如果說 RequireJS 是 Prototype 類庫的話,則 Sea.js 致力於成為 jQuery 類庫。

還不夠

這里有一個圖文並茂的 “栗子” SeaJS與RequireJS最大的區別,當然樓主的觀點更贊成這個“栗子”: LABjs、RequireJS、SeaJS 哪個最好用?為什么?

結論: 這兩個加載器和標准沒有優劣之分,只有差別。具體還是要根據實際情況進行選擇;



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM