JavaScript 模組化及 SeaJs 原始碼分析
網頁的結構越來越複雜,簡直可以看做一個簡單APP,如果還像以前那樣把所有的程式碼都放到一個檔案裡面會有一些問題:
全域性變數互相影響
JavaScript檔案變大,影響載入速度
結構混亂、很難維護
和後端(比如Java)比較就可以看出明顯的差距。2009年Ryan Dahl建立了node.js專案,將JavaScript用於伺服器程式設計,這標誌“JS模組化程式設計”正式誕生。
基本原理
模組就是一些功能的集合,那麼可以將一個大檔案分割成一些小檔案,在各個檔案中定義不同的功能,然後在HTML中引入:
var module1 = new Object({ _count : 0, m1 : function (){ //... }, m2 : function (){ //... } });
這樣做的壞處是:把模組中所有的成員都暴露了!我們知道函式的本地變數是沒法從外面進行訪問的,那麼可以用立即執行函式來最佳化:
var module1 = (function(){ var _count = 0; var m1 = function(){ //... }; var m2 = function(){ //... }; return { m1 : m1, m2 : m2 }; })();
大家定義模組的方式可能五花八門,如果都能按照一定的規範來,那好處會非常大:可以互相引用!
模組規範
在node.js中定義math.js模組如下:
function add(a, b){ return a + b; } exports.add = add;
在其他模組中使用的時候使用全域性require函式載入即可:
var math = require('math'); math.add(2,3);
在伺服器上同步require是沒有問題的,但是瀏覽器在網路環境就不能這麼玩了,於是有了非同步的:
require(['math'], function (math) {// require([module], callback); math.add(2, 3); });
模組的定義方式如下(模組可以依賴其他的模組):
define(function (){ // define([module], callback); var add = function (x,y){ return x+y; }; return { add: add }; });
用RequireJS可以載入很多其他資源(看),很好很強大!在工作中用的比較多的是,所使用的規範稱為CMD,推崇(應該是指非同步模式):
as lazy as possible!
對於依賴的模組的處理方式和AMD的區別在於:
AMD是提前執行(依賴前置),CMD是延遲執行(依賴就近)。
在CMD中定義模組的方式如下:
define(function(require, exports, module) { var a = require('./a'); a.doSomething(); var b = require('./b'); b.doSomething(); });
使用方式直接看,這裡就不贅述了!
SeaJS原始碼分析
剛接觸模組化的時候感覺這個太簡單了,不就是:
建立script標籤的時候設定一下onload和src!
事實上是這樣的,但也不完全是!下面來開始看SeaJS的程式碼()。一個模組在載入的過程中可能經歷下面幾種狀態:
var STATUS = Module.STATUS = { // 1 - The `module.uri` is being fetched FETCHING: 1, // 2 - The meta data has been saved to cachedMods SAVED: 2, // 3 - The `module.dependencies` are being loaded LOADING: 3, // 4 - The module are ready to execute LOADED: 4, // 5 - The module is being executed EXECUTING: 5, // 6 - The `module.exports` is available EXECUTED: 6, // 7 - 404 ERROR: 7}
記憶體中用Modul
物件來維護模組的資訊:
function Module(uri, deps) { this.uri = uri this.dependencies = deps || [] // 依賴模組ID列表 this.deps = {} // 依賴模組Module物件列表 this.status = 0 // 狀態 this._entry = [] // 在模組載入完成之後需要呼叫callback的模組}
在頁面上啟動模組系統需要使用seajs.use
方法:
seajs.use(‘./main’, function(main) {// 依賴及回撥方法 main.init(); });
載入過程的整體邏輯可以在Module.prototype.load
中看到:
Module.prototype.load = function() { var mod = this if (mod.status >= STATUS.LOADING) { return } mod.status = STATUS.LOADING var uris = mod.resolve() // 解析依賴模組的URL地址 emit("load", uris) for (var i = 0, len = uris.length; i總體上邏輯很順就不講了,唯一比較繞的就是
_entry
陣列了。網上沒有找到比較通俗易懂的文章,於是看著程式碼連蒙帶猜地大概看懂了,其實只要記住它的目標即可:當依賴的所有模組載入完成後執行回撥函式!
換種說法:
陣列_entry中儲存了當前模組載入完成之後、哪些模組的依賴可能載入完成的列表(依賴的反向關係)!
舉個例子,模組A依賴於模組B、C、D,那麼經過pass之後的狀態如下:
此時A中的
remain
為3,也就是說它還有三個依賴的模組沒有載入完成!而如果模組B依賴模組E、F,那麼在它load的時候會將A也傳遞出去:有幾個細節:
已經載入完成的模組不會被傳播;
已經傳播過一次的模組不會再次傳播;
如果依賴的模組正在載入那麼會遞迴傳播;
維護好依賴關係之後就可以透過
Module.prototype.fetch
來載入模組,有兩種sendRequest
的實現方式:
importScripts
script
然後根據結果執行
load
或者error
方法。依賴的所有模組都載入完成後就會執行onload
方法:Module.prototype.onload = function() { var mod = this mod.status = STATUS.LOADED for (var i = 0, len = (mod._entry || []).length; i其中
--entry.remain
就相當於告訴entry對應的模組:你的依賴列表裡面已經有一個完成了!而entry.remain === 0
則說明它所依賴的所有的模組都已經載入完成了!那麼此時將執行回撥函式:for (var i = 0, len = uris.length; i指令碼下載完成之後會馬上執行
define
方法來維護模組的資訊:沒有顯式地指定dependencies時會用parseDependencies來用正則匹配方法中的require()片段(指定依賴列表是個好習慣)。
接著執行
factory
方法來生成模組的資料:var exports = isFunction(factory) ? factory.call(mod.exports = {}, require, mod.exports, mod) : factory然後執行你在seajs.use中定義的
callback
方法:if (callback) { callback.apply(global, exports) }當你寫的模組程式碼中require時,每次都會執行factory方法:
function require(id) { var m = mod.deps[id] || Module.get(require.resolve(id)) if (m.status == STATUS.ERROR) { throw new Error('module was broken: ' + m.uri) } return m.exec() }到這裡核心的邏輯基本上講完了,補一張狀態的轉換圖:
以後在用的時候就可以解釋一些詭異的問題了!
總結
模組化非常好用,因此在ECMAScript 6中也開始支援,但是瀏覽器支援還是比較堪憂的~~
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4650/viewspace-2803051/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Seajs原始碼解讀JS原始碼
- mybaits原始碼分析--binding模組(五)AI原始碼
- JavaScript模組化JavaScript
- mybaits原始碼分析--日誌模組(四)AI原始碼
- mybaits原始碼分析--快取模組(六)AI原始碼快取
- Swoole 原始碼分析——Client模組之Send原始碼client
- Swoole 原始碼分析——Client模組之Connect原始碼client
- Swoole 原始碼分析——Client模組之Recv原始碼client
- Django(49)drf解析模組原始碼分析Django原始碼
- Django(51)drf渲染模組原始碼分析Django原始碼
- 從原始碼分析Node的Cluster模組原始碼
- Swoole 原始碼分析——Reactor 模組之 ReactorEpoll原始碼React
- Javascript 模組化指北JavaScript
- (一) Mybatis原始碼分析-解析器模組MyBatis原始碼
- Swoole 原始碼分析——Server模組之OpenSSL (上)原始碼Server
- Django(48)drf請求模組原始碼分析Django原始碼
- beego cache模組原始碼分析筆記四Go原始碼筆記
- Swoole 原始碼分析——Server 模組之 OpenSSL (下)原始碼Server
- Swoole 原始碼分析——Server 模組之 OpenSSL (上)原始碼Server
- TiFlash 原始碼閱讀(四)TiFlash DDL 模組設計及實現分析原始碼
- 以太坊原始碼分析(1)go-ethereum的設計思路及模組組織形式原始碼Go
- JavaScript 模組化前世今生JavaScript
- JavaScript 中的模組化JavaScript
- JavaScript 模組化總結JavaScript
- javascript模組化簡介JavaScript
- JavaScript模組化規範JavaScript
- mybaits原始碼分析--型別轉換模組(三)AI原始碼型別
- Swoole 原始碼分析——鎖與訊號量模組原始碼
- Swoole 原始碼分析——基礎模組之 Pipe 管道原始碼
- btcpool礦池原始碼分析(3)-BlockMaker模組解析TCP原始碼BloC
- btcpool礦池原始碼分析(4)-GbtMaker模組解析TCP原始碼
- btcpool礦池原始碼分析(5)-JobMaker模組解析TCP原始碼
- btcpool礦池原始碼分析(6)-nmcauxmaker模組解析TCP原始碼UX
- btcpool礦池原始碼分析(6)-PoolWatcher模組解析TCP原始碼
- btcpool礦池原始碼分析(7)-sharelogger模組解析TCP原始碼
- btcpool礦池原始碼分析(9)-statshttpd模組解析TCP原始碼httpd
- btcpool礦池原始碼分析(10)-StratumServer模組解析TCP原始碼Server
- JavaScript模組化的演變JavaScript