seajs的CMD模式的优势以及使用
之前有一篇博客非常詳細的介紹了sea.js的加載流程,以及源代碼實現,鏈接地址:http://www.cnblogs.com/chaojidan/p/4123980.html
這篇博客我主要講下sea.js的介紹和使用。
首先,先介紹下sea.js的CMD規范,以及跟其他規范的區別。
CommonJS 原來叫 ServerJS,推出?Modules/1.0?規范后,在 Node.js 等環境下取得了很不錯的實踐。
09年下半年這幫充滿干勁的小伙子們想把 ServerJS 的成功經驗進一步推廣到瀏覽器端,于是將社區改名叫 CommonJS,同時激烈爭論 Modules 的下一版規范。分歧和沖突由此誕生,逐步形成了三大流派:
第二流派:AMD 與 RequireJS
AMD 風格下,通過參數傳入依賴模塊,破壞了就近聲明 (需要時,才聲明)原則。比如:
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {? ??? // 等于在最前面聲明并初始化了要用到的所有模塊 ?? if (false) { ?????? // 即便沒用到某個模塊 b,但 b 還是提前執行了 ?????? b.foo() ?? }? })第三流派:Modules/2.0 CMD模塊
CMD 里,默認推薦的是
define(function(require, exports, module) { //a,b模塊只下載好了,并且只執行了模塊中的define方法,而define方法中的function要等到require時,才會執行 ? var a = require('a'); //延遲執行了a,b模塊 ? var b = require('b'); ? // do sth })?
區別:
1. 對于依賴的模塊,AMD 是提前執行,CMD 是延遲執行。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible.
2. CMD 推崇依賴就近,AMD 推崇依賴前置。看代碼:
// CMD
define(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();
})?
雖然 AMD 也支持 CMD 的寫法,同時還支持將 require 作為依賴項傳遞,但 RequireJS 的作者默認是最喜歡上面的寫法,也是官方文檔里默認的模塊定義寫法。
3. AMD 的 API 默認是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。
比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,沒有全局 require,而是根據模塊系統的完備性,提供 seajs.use 來實現模塊系統的加載啟動。CMD 里,每個 API 都簡單純粹。
CMD 可以使得構建時的復雜度降低。
目前 Sea.js 擁有 plugin-combo 插件,模塊的合并可以放在線上動態做。有些情況下(比較容易出現),動態 combo 的地址會很長:
https://a.alipaybojects.com/??path/to/a.js,path/to/b.js..................path/to/long-url.js
當 url 地址很長時,超過 2083(好像是這個值),在 IE 以及一些服務器配置下,過長的 url 會出問題。這時經典的解決辦法是將 url 拆分成多段:
https://a.alipaybojects.com/??path/to/a.js,path/to/b.js..................path/to/u.js
https://a.alipaybojects.com/??path/to/f.js,path/to/g.js..................path/to/long-url.js
拆分后,在 CMD 規范下,上面兩個 url 可以并發同時請求,誰先返回都沒問題。但在 AMD 下,上面的需求,就掛了,很難實現。
你會說 RequireJS 鼓勵的是項目上線前,通過構建工具先構建好,不需要線上 combo,也就不會遇到上面的問題。
Sea.js 放得更寬泛,提前合并好,還是線上時才動態 combo,對 CMD 模塊來說都可行。很多時候,combo 真心省事,也更自然。前端開發并非處處要像 Java 一樣引入嚴格的構建過程。
CMD 的懶執行策略,也更有利于頁面性能。
RequireJS 2.0 后,不少理念也在悄悄地發生著變化,現在好像也支持懶執行了。
?
然后,介紹下sea.js的方法和使用。
type="text/javascript" src="js/seajs/2.0.0/sea-debug.js?t=123" data-config="sea-js-config.js?t=123"
上面的data-config是指sea.js的配置文件的路徑。還有一個屬性是data-main,它是項目的起始模塊,如果定義了會先執行此模塊。data-main是可選項。
首先我們來看看sea-js-config.js
seajs.config({
? ? // 配置插件
? ?plugins: ['shim'],
? ? // 配置別名
? ?alias: {
? ? ? // 配置 jquery 的 shim 配置,這樣我們就可以通過 require('jquery') 來獲取 jQuery
? ? 'jquery': {
? ? ? ? src: 'libs/jquery/1.9.1/jquery.js', //注意,這里是從sea.js所在目錄的上兩節目錄開始查找文件
? ? ? ? ? ?exports: 'jQuery'
? ? ? ?}
? ?}
});
plugins選項配置插件,這里使用了shim插件。由于jquery不是一個標準的CMD模塊,所以直接加載jquery是錯誤的。這里使用了shim插件,會自動把jquery轉換成一個標準的CMD模塊。不用人工改動jquery源碼。alias是配置別名,方便加載的。
看個例子:
項目主模塊app.js
define(function(require, exports, module) {
//加載jquery, 并把它$設為全局變量
? window.$ = window.jQuery = $ = require('jquery');
? //定義一個全局的模塊加載函數.module為模塊名,options為參數
? exports.script_load = function(module, options) {
? ? ?? //使用require.async異步加載模塊。模塊需要提供一個固定的對外調用接口,這里定義為run。
? ? ? require.async('modules/' + module, function(module) {
? if (typeof(module.run) === 'function') {
? ? ? ? ? ? ? module.run(options);
? }
? ? ? ? });
? }
? window.script_load = exports.script_load
});
上面我們加載了jquery, 并且定義了一個模塊加載函數。現在我們在html頁面加入如下代碼:
<script type="text/javascript">
? ? ?seajs.use('modules/app', function(app) {
? ? ? ? ? app.script_load('index');
});
</script>
use方法執行時,會先加載app模塊,加載并執行完后,就進入function中,這時就會調用app.script_load方法,此方法就會去加載index模塊,加載完成后,執行index中的代碼,index中會返回run方法。index執行完畢后,會調用require.async的回調方法:
if (typeof(module.run) === 'function') {
? ? ? ? ? ? ? module.run(options);
}
因此index模塊中返回了run方法,因此就執行index中的run方法。
index.js
define(function(require, exports, module) {
? exports.run = function() {
? ? ? $('#alert').click(function() {
alert('彈出了一個框!');
? ? ? });
}
});
?
SeaJS中使用“define”函數定義一個模塊,define可以接收三個參數,
define可以接收的參數分別是模塊ID,依賴模塊數組及工廠函數。
我閱讀源代碼后發現define對于不同參數個數的解析規則如下:
如果只有一個參數,則賦值給factory。
如果有兩個參數,第二個賦值給factory;第一個如果是array則賦值給deps,否則賦值給id。
如果有三個參數,則分別賦值給id,deps和factory。
id是一個模塊的標識字符串,define只有一個參數時,id會被默認賦值為此js文件的絕對路徑。
如example.com下的a.js文件中使用define定義模塊,則這個模塊的ID會賦值為 http://example.com/a.js ,沒有特別的必要建議不要傳入id。deps一般也不需要傳入,需要用到的模塊用require加載即可。
工廠函數function是模塊的主體和重點。在只傳遞一個參數給define時(推薦寫法),這個參數就是工廠函數,此時工廠函數的三個參數分別是:
? require——模塊加載函數,用于記載依賴模塊。
? exports——接口點,將數據或方法定義在其上則將其暴露給外部調用。
? module——模塊的元數據。
module是一個對象,存儲了模塊的元信息,具體如下:
? module.id——模塊的ID。
? module.dependencies——一個數組,存儲了此模塊依賴的所有模塊的ID列表。
? module.exports——與exports指向同一個對象。
?
三種編寫模塊的模式:
第一種定義模塊的模式是基于exports的模式:
define(function(require, exports, module) {
var a = require('a'); //引入a模塊
? ? ?var b = require('b'); //引入b模塊
? ? ?var data1 = 1; //私有數據
var func1 = function() { //私有方法
return a.run(data1);
}
? ? ?exports.data2 = 2; //公共數據
? ? ?exports.func2 = function() { //公共方法
return 'hello';
}
});
上面是一種比較“正宗”的模塊定義模式。除了將公共數據和方法附加在exports上,也可以直接返回一個對象表示模塊,如下面的代碼與上面的代碼功能相同:(第二種)
define(function(require, exports, module) {
var a = require('a'); //引入a模塊
? ? ?var b = require('b'); //引入b模塊
? ? ?var data1 = 1; //私有數據
var func1 = function() { //私有方法
return a.run(data1);
}
? return {
data2: 2,
? ? ? ? ? ?func2: function() {
? ? ? ? ? ? ? return 'hello';
}
};
});
如果模塊定義沒有其它代碼,只返回一個對象,還可以有如下簡化寫法。第三種方法對于定義純JSON數據的模塊非常合適。
define({
????data: 1,
????func: function() {
????????return 'hello';
? ?}
});
?
絕對地址——給出js文件的絕對路徑。如
require("http://example/js/a");
就代表載入?http://example/js/a.js?。
基址地址——如果載入字符串標識既不是絕對路徑也不是以”./”開頭的相對地址,則相對SeaJS全局配置中的“base”來尋址。
注意上面在載入模塊時都不用傳遞后綴名“.js”,SeaJS會自動添加“.js”。但是下面三種情況下不會添加:
載入css時,如
require("./module1-style.css");
路徑中含有”?”時,如
require(<a href="http://example/js/a.json?cb=func">http://example/js/a.json?cb=func</a>);
路徑以”#”結尾時,如
require("http://example/js/a.json#");
根據應用場景的不同,SeaJS提供了三個載入模塊的API,分別是seajs.use,require和require.async。
seajs.use主要用于載入入口模塊。入口模塊相當于C程序的main函數,同時也是整個模塊依賴樹的根。seajs.use用法如下:
//單一模式
seajs.use('./a');
//回調模式
seajs.use('./a', function(a) {
?? a.run();
});
//多模塊模式
seajs.use(['./a', './b'], function(a, b) {
? ? a.run();
b.run();
?});
一般seajs.use只用在頁面載入入口模塊,SeaJS會順著入口模塊解析所有依賴模塊并將它們加載。如果入口模塊只有一個,也可以通過給引入sea.js的script標簽加入”data-main”屬性來省略seajs.use,例如,
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
?<title>TinyApp</title>
</head>
<body>
? <p class="content"></p>
? <script src="./sea.js" data-main="./init"></script>
</body>
</html>
?
傳給require的路徑標識必須是字符串字面量,不能是表達式,如下面使用require的方法是錯誤的:
?require('module' + '1');
?require('Module'.toLowerCase());
這都會造成SeaJS無法進行正確的正則匹配以下載相應的js文件。
上文說過SeaJS會在html頁面打開時通過靜態分析,一次性下載所有需要的js文件,如果想要某個js文件在用到時才下載,可以使用require.async。
require.async('/path/to/module/file', function(m) {
//code of callback...
});
這樣只有在用到這個模塊時,對應的js文件才會被下載,也就實現了JavaScript代碼的按需加載。
SeaJS提供了一個seajs.config方法可以設置全局配置,接收一個表示全局配置的配置對象。
seajs.config({
base: 'path/to/jslib/',
alias: {
? ? ? ? 'app': 'path/to/app/'
},
charset: 'utf-8',
timeout: 20000,
debug: false
});
其中base表示基址尋址時的基址路徑。例如base設置為 http://example.com/js/3-party/ ,則
var $ = require('jquery');
會載入 http://example.com/js/3-party/jquery.js 。
alias可以對較長的常用路徑設置縮寫。
charset表示下載js時script標簽的charset屬性。
timeout表示下載文件的最大時長,以毫秒為單位。
debug表示是否工作在調試模式下。
要將現有JS庫如jQuery與SeaJS一起使用,只需根據SeaJS的的模塊定義規則對現有庫進行一個封裝。例如,下面是對jQuery的封裝方法:
define(function() {
? //{{{jQuery原有代碼開始
?//}}}jQuery原有代碼結束
? return $.noConflict();
});
?
特別注意:下面這種寫法是錯誤的!
define(function(require, exports) {
// 錯誤用法!!!
exports = {
foo: 'bar',
doSomething: function() {}
};
});
正確的寫法是用 return 或者給 module.exports 賦值:
define(function(require, exports, module) {
// 正確寫法
module.exports = {
foo: 'bar',
doSomething: function() {}
};
});
提示:exports 僅僅是 module.exports 的一個引用。在 factory 內部給 exports 重新賦值時,并不會改變 module.exports 的值。因此給 exports 賦值是無效的,不能用來更改模塊接口。
傳給 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 的賦值需要同步執行,不能放在回調函數里。下面這樣是不行的:
define(function(require, exports, module) {
// 錯誤用法
setTimeout(function() {
module.exports = { a: "hello" };
}, 0);
});
seajs.config({
alias: {
'jquery': 'jquery/1.7.2/jquery-debug.js'
}
});
seajs.use(['./a','jquery'],function(a,$){
var num = a.a;
$('#J_A').text(num);
})
use方法將會從我們的config配置信息中查看 ,是否有預先需要被加載的模塊。如果有,就先加載,沒有就加載a和jquery模塊。
轉載于:https://www.cnblogs.com/chaojidan/p/4147925.html
總結
以上是生活随笔為你收集整理的seajs的CMD模式的优势以及使用的全部內容,希望文章能夠幫你解決所遇到的問題。