先列舉下一些著名言論:
“我想定義一個 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 三個參數
- 如何學習
如果你順手 NodeJS ,那么你會愛上它,如果你真愛上了它,那你可以移步這里: 5 分鍾上手 Sea.js 或者是這里: 一步步學會使用 Sea.js 2.0,或者先看下 2.0的更新 。但是有個問題需要你慎重 打包 ! SeaJS的打包確實有門檻哦~ ( 不論 grunt 還是 spm )
打個廣告:自然 SeaJS 也歡迎你的 issues,不過 提問是一門學問: 如何向開源社區提問題
前端模塊化開發的歷史
既然是 回顧 那就應該好文不斷: 《 前端模塊化開發那點歷史 》
AMD規范及其代表: RequireJS
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 哪個最好用?為什么?
結論: 這兩個加載器和標准沒有優劣之分,只有差別。具體還是要根據實際情況進行選擇;
完
