首頁  >  文章  >  web前端  >  JavaScript模組化開發庫之SeaJS

JavaScript模組化開發庫之SeaJS

高洛峰
高洛峰原創
2016-11-26 09:44:211337瀏覽

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(uncel, type, fn){),
:  , type){}
});
  
這樣就寫了一個事件模組,這和寫一個單例沒有差別。更多的時候用該方式定義純資料模組。它類似於
 var E = {
    addEvent: function(el, type, fn){},
    removeEvent: function(el, type, fn){}, 
  
2、簡單的包裝模組
 define(function() {
    // 一些內部輔助函數
    // ...
     function removeEvent() {
        // ..
    }
    function fireEvent() {
        // ..
    }
   : removeEvent,
        fireEvent: fireEvent
    };
});
  
您懂的,在這個匿名函數中可以做很多事情。最後只需公開必要的介面。它類似
 var E = function() {
    // 一些內部輔助函數
    // ...
    function addEvent() {
          // ..
    }
    function fireEvent() {
        // ..
    }
    return {
addEvent: addEvent,
        removeEvent: removeEvent,
        fireEvent:fireEvent
   JS風格(Modules/1.1.1),改寫下與「方式2」等價的。
 define(function(require, exports) {
    // 一些內部輔助函數
    // ...
    function addEvent() { 
        // ..
    }
function fireEvent() {
        // ..
    }
    // 使用exports導出模組接口,而非返回一個物件
   addEvent = fireEvent;
}) ;
  
可以看到與「方式2」區別在於:
1:匿名函數有兩個參數require、exports。
2:導出介面不是return一個物件而是使用exports。
而exports不正是NodeJS的風格嗎? 細心的同學可能會發現這個範例中require參數沒有用到,這正是下面要講的。
 
4、依賴的模組
 
SeaJS中「依賴」都需要使用require函數去獲取,雖然SeaJS的define的第二個參數deps也有「依賴」的意思,但它是提供的。此外,SeaJS的require是作為參數傳入匿名函數內的,RequireJS的require則是全域變數。 www.2cto.com
 
上面定義的是一個沒有依賴的模組,以下是有依賴的模組。
 define(function(require, exports) {
    var cache = require('cache');
     
    // 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');
     
    // unbind = unbind;
    exports.trigger = trigger;
});

Firebug網路請求如下
 
會看到依賴的「cache」沒有被加載,當然JS肯定會報錯了。

b、只把匿名函數的形參改成req,函數內部仍使用require。
 define(function(req, exports) {
    var cache = require('cache');
     
    // unbind = unbind;
    exports.trigger = trigger;
});
  
看網路請求
 
這次「cache」模組竟然請求下來了。

 

仔細看上面的匿名函數程式碼中require沒聲明,且形參req而非require。那
?
1 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");
  
c、修改exports為expodef
( , expo) {
    var cache = require('cache');
     
    // ...
       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');
     
    // ...🠎      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)。



陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn