JavaScript模塊化開發庫之SeaJS


SeaJS由國內的牛人lifesinger開發。目前版本是1.1.1,源碼不到1500行,壓縮后才4k,質量極高。

這篇會講述SeaJS的一些基本用法,不會面面俱到,但會就個人的理解講述官方文檔沒有提到的一些細節。

 

一、SeaJS的全局接口

 

SeaJS向全局公開了兩個標識符: seajs 和 define。

 

如果你的項目中已經用了標識符seajs,又不想改。這時SeaJS可以讓出全局的seajs。如

var boot = seajs.noConflict();  

這時boot就相當於先前的seajs。

 

如果你的項目中連標識符define也用到了,也不想改。SeaJS是很寬容的,它的define也可以讓出。如

var boot = seajs.noConflict(true);  

較上面僅多傳了一個true。這時全局的define也沒了。這時需要用boot.define來代替之前的define。

 

用過jQuery的同學應該很熟悉$.noConflict方法,SeaJS的noConflict與之類似。

 

二、SeaJS的模塊寫法

 

SeaJS默認使用全局的define函數寫模塊(可把define當成語法關鍵字),define定義了三個形參id, deps, factory。

 

define(id?, deps?, factory);

 

這個define很容易讓你想起AMD的唯一API:define函數。 或者說讓人費解,導致搞不懂SeaJS和 RequireJS define的區別。

 

它們都有個全局的define,形參都是三個,且對應的形參名也一樣,會誤認為SeaJS也是AMD的實現。

 

事實上SeaJS和RequireJS的define前兩個參數的確一樣。

 

id都為字符串,都遵循 Module Identifiers。deps都是指依賴模塊,類型都為數組。區別僅在於第三個參數factory,雖然類型也都是函數,但factory的參數意義卻不同。

 

RequireJS中factory的參數有兩種情況

a、和deps(數組)元素一一對應。即deps有幾個,factory的實參就有幾個。

define(['a', 'b'], function(a, b){
	// todo
});

  

b、固定為require,exports, module(modules/wrappings格式)。

define(function(require, exports, module){
	// todo
});

  

這種方式是RequireJS后期向 Modules/Wrappings 的妥協,即兼容了它。而SeaJS的define僅支持RequireJS的第二種寫法:Modules/Wrappings。

注意:SeaJS遵循的是 Modules/Wrappings 和 Modules/1.1.1。這兩個規范中都沒有提到define關鍵字,Modules/Wrapping中要求定義模塊使用module.declare而非define。而恰恰只有AMD規范中有define的定義。即雖然SeaJS不是AMD的實現,但它卻采用了讓人極容易誤解的標識符define。

 

 

說了這么多,還沒開始寫一個模塊。下面我們從最簡單的開始

1、簡單模塊

define({
	addEvent: function(el, type, fn){},
	removeEvent: function(el, type, fn){},
	fireEvent: function(el, type){}
});

  

這樣就寫了一個事件模塊,這和寫一個單例沒有區別。更多的時候用該方式定義純數據模塊。它類似於

var E = {
	addEvent: function(el, type, fn){},
	removeEvent: function(el, type, fn){},
	fireEvent: function(el, type){}
};

  

2、簡單的包裝模塊

define(function() {
	// 一些內部輔助函數
	// ...
	function addEvent() {
		// ..
	}
	function removeEvent() {
		// ..
	}
	function fireEvent() {
		// ..
	}
	return {
		addEvent: addEvent,
		removeEvent: removeEvent,
		fireEvent: fireEvent
	};
});

  

您懂的,在這個匿名函數中可以做很多事情。最后只需公開必要的接口。它類似於

var E = function() {
	// 一些內部輔助函數
	// ...
	function addEvent() {
		// ..
	}
	function removeEvent() {
		// ..
	}
	function fireEvent() {
		// ..
	}
	return {
		addEvent: addEvent,
		removeEvent: removeEvent,
		fireEvent: fireEvent
	};
}();

  

3、NodeJS風格的包裝模塊

 

上面兩種寫法看不到一絲NodeJS風格(Modules/1.1.1),改寫下與“方式2”等價的。

define(function(require, exports) {
	// 一些內部輔助函數
	// ...
	function addEvent() {
		// ..
	}
	function removeEvent() {
		// ..
	}
	function fireEvent() {
		// ..
	}
	// 使用exports導出模塊接口,而非返回一個對象
	exports.addEvent = addEvent;
	exports.addEvent = removeEvent;
	exports.addEvent = fireEvent;
});

  

可以看到與“方式2”區別在於:

1:匿名函數有兩個參數require、exports。

2:導出接口不是return一個對象而是使用exports。

而exports不正是NodeJS的風格嗎? 細心的同學可能發現這個示例中require參數沒有用到,這正是下面要講的。

 

4、有依賴的模塊

 

SeaJS中“依賴”都需要使用require函數去獲取,雖然SeaJS的define的第二個參數deps也有“依賴”的意思,但它是提供打包工具(SPM)用的。此外,SeaJS的require是作為參數傳入匿名函數內的,RequireJS的require則是全局變量。

 

上面定義的是一個沒有依賴的模塊,以下是有依賴的模塊。

define(function(require, exports) {
	var cache = require('cache');
	
	// ...
	
	exports.bind = bind;
	exports.unbind = unbind;
	exports.trigger = trigger;
});

  

該事件模塊依賴於cache模塊,函數有兩個形參require和exports。拋開外層的匿名函數及define,它就是標准的NodeJS格式:使用require函數取依賴模塊,使用exports導出現有模塊接口。

實際上在SeaJS中具有依賴的模塊必須按“方式4”寫,即必須是包裝模塊,且匿名函數的第一個參數必須是標識符 “require”。即可以把require當初語法關鍵字來使用,雖然它不是全局的。

 

下面我們看看匿名函數的參數require和exports的一些有趣現象

a、如果寫的不是require,改成req會是什么結果。

define(function(req, exports) {
	var cache = req('cache');
	
	// ...
	
	exports.bind = bind;
	exports.unbind = unbind;
	exports.trigger = trigger;
});

 

Firebug網絡請求如下

會看到依賴的“cache”沒有被加載,當然JS肯定會報錯了。

 

b、只把匿名函數的形參改成req,函數內部仍然使用require。

define(function(req, exports) {
	var cache = require('cache');
	
	// ...
	
	exports.bind = bind;
	exports.unbind = unbind;
	exports.trigger = trigger;
});

  

看網絡請求

這次“cache”模塊竟然請求下來了。

 

仔細看上面的匿名函數代碼中require沒聲明,且形參是req而非require。那

var cache = require('cache');

中的require從何而來?

 

看SeaJS源碼可知,它的define函數中會取該匿名函數的toString,使用正則匹配解析出其中的“cache”(私有的parseDependencies函數)。

 

我們也看到,雖然cache請求下來了,卻仍然報錯,因為在執行階段require是未定義的。因此寫依賴模塊時匿名函數的第一個參數必須為require且不能更改。

 

正因為使用factory.toString和正則解析依賴,因此require的參數不能是表達式,如

// require的參數不能是表達式運算
require("ui-" + "dialog"); 

 

也不能使用require的別名,如

// 不能將require賦值給另外一個變量
var req = require;
req("ui-dialog");

 

另據說Opera移動版不支持function的toString,這樣SeaJS無法在該版本中使用了(未測)。

 

c、修改exports為expo

define(function(require, expo) {
	var cache = require('cache');
	
	// ...
	
	expo.bind = bind;
	expo.unbind = unbind;
	expo.trigger = trigger;
});

  

運行是沒有問題的。即第二個參數“exports”是可以自定義的。顯然SeaJS不贊成改“exports”為其它,這樣明顯破壞了NodeJS風格(Modules/1.1.1)的模塊規范---它們正是使用“exports”導出模塊接口。但這點在SeaJS中卻無法被強制執行,只能是人為約定。

 

5、混合寫法的模塊

 

上面已經介紹了各種情形中的模塊寫法。為了與NodeJS風格保持一致:使用require獲取“依賴”,使用exports導出“接口”。SeaJS在獲取依賴這一塊做了限制,即必須使用require。但導出則不一定非得使用exports,即exports可以改為其它。甚至還可以直接使用 “返回值”。

define(function(require) {
	var cache = require('cache');
	
	// ...
	
	// 使用返回值導出接口
	return {
		bind: function() {},
		unbind: function() {},
		fire: function() {}
	};
});

  

我們知道在NodeJS中模塊只能是一個對象。即總是往exports上掛方法。SeaJS中如果使用exports導出接口,那么也一樣,模塊也只能是JS對象。如果使用“返回值”導出接口的話,那么模塊可以是任意的JS類型。如下將返回一個函數類型的模塊。

define(function(require) {
	var cache = require('cache');
	
	function ad() {
		//...
	}
	
	// 函數類型的模塊
	return ad;
});

  

三、SeaJS的加載方式

 

雖然它提供各種方式(同步異步)加載,最簡單的莫過於直接在頁面中寫script標簽。引入SeaJS后,入門多數時候就是seajs.use方法。

seajs.use有兩個參數,第一個參數可以為字符串(模塊名)或數組(多個模塊)。第二個參數是回調函數。模塊加載后的回調。回調函數的參數與第一個參數一一對應。

seajs.use('dom', function(dom) {
	// todo with dom
});

  

如下將在回調函數中使用dom模塊。當然它也提供了快捷方式data-main(同RequireJS)。

 

 

下面的demo中寫了一個完整的事件模塊event,它依賴於cache模塊。

seajs-demo.zip


免責聲明!

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



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