初識 SeaJS

jopen 12年前發布 | 25K 次閱讀 SeaJS JavaScript開發

最近NodeJS和CommonJS真是好火啊,前端真的不再如從前那么“單純”了,從此需要學習掌握更多的傳統軟件開發的知識和技能,也就是從前后端開發做的很多工作會逐漸前移到前端工程師。這里為什么要把NodeJS和CommonJS放在一起說呢,主要是我對里面的模塊機制十分感興趣,正好NodeJS中的模塊實現方式也遵循CommonJS中的相關規范。

神奇的 require

var http = require('http'); 

http.createServer(function (req, res) {     
    res.writeHead(200, {'Content-Type': 'text/plain'});     
    res.end('Hello World\n'); 
}).listen(1337, "127.0.0.1"); 

console.log('Server running at http://127.0.0.1:1337/');

這是NodeJS首頁上的一段代碼,我們注意看一下require這個函數的用法,這里的代碼需要http這個模塊,因此就var http = require('http');但是令我比較疑惑的就是明明require函數是個異步的調用,因為http的模塊文件不可能同步在這里堵塞加載執行(當然可能有人會說使用同步的AJAX請求,但是我們同樣需要支持跨域問題),雖然這段代碼是NodeJS的代碼,其主要是運行在服務器端,使用V8引擎驅動,可以使用一些手段讓requrie的模塊同步加載執行,但是對于一個Web應用看到這樣的代碼一定會疑惑為什么這里不需要一個回調函數~好吧,就此認為這就是NodeJS在服務器端的神奇之處~~~那么繼續看下面一段代碼

define(function(require, exports, module) {       
    // 獲取依賴的模塊:     
    var $ = require('jquery');       
    // 向外提供接口:     
    exports.someMethod = someFunction;   
}); 

這段代碼是來自SeaJS首頁的一段代碼(額,我們的主角終于現身了~)這里再次出現了var $ = require('jquery');這樣一段 require 函數,這里同樣會有剛剛上述的問題,明明是個異步的需求怎么在這里可以使用同步的寫法呢?一種假設是 require 函數在這里已經事先準備好了 jquery 的對象(加載和執行 jquery.js ),但是這有什么策略么?不可能所有支持的模塊都必須這樣提前加載,況且以后根本無法方便擴展,添加自己的模塊啊;第二種假設就是 jquery 對象真是在這里第一次出現,然后下載文件,執行文件的~但是據我所知,只能使用同步的 AJAX 才能做到這樣,但是跨域的問題是最致命的問題,這里不可能沒有處理啊??!!糾結啊,甚是糾結啊~一個require竟然有如此神奇的功力~

撥開 require 的神秘外衣

介于NodeJS的代碼性質以及難度,這里暫時沒有去研究其 require 的實現方法,限制著重談談SeaJS。關于SeaJS是什么,建議首先訪問其網站,大致了解一下,在其中自然也提到了不少CommonJS的規范問題。

為了更好地探究這個神奇的require函數,我首先從github上拉到了最新的SeaJS源代碼。大致看下 seajs 代碼執行后會產生些什么,如下圖:

初識 SeaJS

是的,沒看錯,seajs 這個對象暴露出來的接口就這么簡單幾個,但是不能僅僅因為暴露的接口簡單精巧就忽視了其豐富的內涵。我們打開其源代碼,大致看一下整個項目結構,這里主要把目光集中在 src 目錄下:

初識 SeaJS

文件命名主要是以 sea 開頭的總引導文件以及 api 文件,fn 開頭的輔助函數文件,util 開頭的工具文件。通過 build.xml 可以了解整個SeaJS發布文件的構成方式。打開 sea.js 文件,可以看到剛剛我提到的輔助函數、工具在項目整個解構中都是以私有方式保存在暴露的 seajs 對象中,但是為什么實際看不到這些私有屬性呢,愿意可以在 sea-api.js 中發現,具體看代碼吧,這里不細說了~總的來說,整個 seajs 充分運用閉包的優勢,把私有變量均保存在閉包中方便使用,在最終的 api 接口中刪除暴露的私有屬性接口,但是由于閉包特性,其內部值仍得以保留,這種方式個人感覺十分新穎,尤其實在做這樣一個多文件項目時,在最終的 api 發布階段再清除不必要的私有接口,既保證了開發時的靈活方便,又保證了數據的安全性。

好像越說離題越遠,那現在回到神秘的 require 函數,那么這個接口是暴露的哪個內部輔助函數呢?看名字以為是在 fn-require.js 中,實則不然,我們再把眼光看遠一些,原來 require 函數的最外層是一個 define 函數,那么打開 fn-define.js,代碼看得云里來霧里去的,但是明確一下現在的目標時找到 require 相關的實現,但是首先,再岔開一下話題,看下 CommonJS 里面的這段規范說明 Modules/Wrappings,哈哈,說到的問題是不是正中下懷啊,正是我們現在關注的方向,繼續看代碼,注意到這樣一個函數:

deps = parseDependencies(factory.toString());   
/* ------------------------------------------ */  
function parseDependencies(code) {     
    var pattern = /\brequire\s*\(\s*['"]?([^'")]*)/g;     
    var ret = [], match;       
    while ((match = pattern.exec(code))) {         
        if (match[1]) {          
            ret.push(match[1]);         
        }     
    }       
    return ret; 
} 

原來如此!define 函數中的依賴關系(實際就是 require 函數的作用),是通過硬解析函數代碼,觀察其中 require 使用情況來決定的啊~雖然沒有想象中的那么神奇,但是也是一種解決方案嘛,說到這里看看上馬的假設,更符合假設一的情況,只是這里自然用不著全部加載其他模塊,通過代碼的“硬解析”來完成依賴需求,在 define 階段即可有機會首先異步加載 require 的模塊,然后當真正 require 時自然也就是同步方式了,這招還真是挺絕妙的啊~

轉自: http://ghsky.com/2011/05/seajs-first-view.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!