摘要:本文主要簡單地解讀一下的源碼和模塊化原理。其中,是這次源碼解讀的核心,但我也會順帶介紹一下其他文件的作用的。對代碼比較簡單,其實就是聲明一下全局的命名空間。然而,真正的核心在于處理模塊依賴的問題。
seajs 簡單介紹
seajs是前端應用模塊化開發的一種很好的解決方案。對于多人協作開發的、復雜龐大的前端項目尤其有用。簡單的介紹不多說,大家可以到seajs的官網seajs.org參看介紹。本文主要簡單地解讀一下seajs的源碼和模塊化原理。如果有描述不實的地方,希望大家指正和交流。
注:本文的解析是基于seajs的2.2.1版本。
解壓seajs之后的src目錄結構如下:
intro.js -- 全局閉包頭部 sea.js -- 基本命名空間 util-lang.js -- 語言增強 util-events.js -- 簡易事件機制 util-path.js -- 路徑處理 util-request.js -- HTTP 請求 util-deps.js -- 依賴提取 module.js -- 核心代碼 config.js -- 配置 outro.js -- 全局閉包尾部
src目錄存放主要的seajs源代碼。各個文件的作用也如上面所示。其中,module.js是這次源碼解讀的核心,但我也會順帶介紹一下其他文件的作用的。
sea.js對代碼比較簡單,其實就是聲明一下全局的seajs命名空間。
intro.js和outro.js則是我們熟悉的匿名函數包裹基本代碼的方式,只是這里比較特別的是,這段匿名函數被拆分成intro.js和outro.js兩個文件。這樣的做法主要是方便調試,在調試的環境下,不引用intro.js和outro.js即可以直接在全局里暴露seajs內部的接口,調試起來比較方便。intro.js和outro.js合并起來的代碼如下:
(function(global, undefined) { if (global.seajs) { return } // .... })(this);
其他文件的用途就不一一重復敘述了,看列表即可。
頁面如何動態加載js文件在解析seajs的源碼和原理之前,讓我們來回憶一下,在沒有seajs或者requirejs的情況下,最原始的動態腳本加載方法是怎樣的。方法很簡單:其實就是創建一個script的標簽,設置了src為你想要加載的腳本url,把script標簽append到Dom里去就想了,so easy!沒錯,絕大部分模塊加載js庫的原理都是如此。
var script = document.createElement("script"); script.setAttribute("src", "example.js"); script.onload = function() { console.log("script loaded!"); }; document.body.appendChild(script);
上述代碼即可以完成一次簡單的動態腳本加載。然而,seajs真正的核心在于處理模塊依賴的問題。在前端JS開發領域,尤其是復雜的web應用,模塊依賴問題一直是令人頭疼的問題。
很簡單的道理,例如A、B、C、D四個模塊對應于A.js、B.js、C.js、D.js四個文件。他們之間的依賴關系例如以下:
A 依賴 B
B 依賴 C和D
問題在于,如何找出模塊里的依賴關系,如何確保A在運行前已經加載了B等等。這些都是前端模塊化和模塊依賴需要解決的問題。
模塊化實現思路seajs的模塊化實現原理,說簡單其實不簡單,說復雜其實也不是很復雜。主要思路可以用下面這一段代碼來說明:
Module.define = function (id, deps, factory) { // 獲取代碼中聲明的依賴關系 deps = parseDependencies(factory.toString()); // 保存 Module.save(); // 匹配到url var url = Module.resolve(id); // 加載腳本 script.url = url; loadScript(); // 執行factory并保存模塊的引用 ... };獲取代碼中聲明的依賴
首先我們來看看如何獲取代碼中聲明需要依賴的模塊。一般情況下,seajs中同步加載模塊的寫法是類似這樣的:
define("scripts/a", function(require, exports, module) { var factory = function() { var moduleB = require("scripts/b"); ... }; module.exports = factory; });
那么需要獲取依賴的信息,我們可以借助Function的toString方法,一個函數的toString方法是會返回函數本身的代碼的(對于JavaScript自身的函數,會返回[native code])。只需要正則表達式來匹配require關鍵詞后面的引用關系即可。所以seajs中函數parseDependencies的寫法就像這樣(這一部分代碼在util-deps.js):
var SLASH_RE = //g var REQUIRE_RE = /"(?:"|[^"])*"|"(?:"|[^"])*"|/*[Ss]*?*/|/(?:/|[^/ ])+/(?=[^/])|//.*|.s*require|(?:^|[^$])requires*(s*([""])(.+?)1s*)/g function parseDependencies(code) { var ret = [] code.replace(SLASH_RE, "") // 匹配require關鍵詞,找出依賴關系 .replace(REQUIRE_RE, function(m, m1, m2) { if (m2) { ret.push(m2) } }) return ret }通過id來匹配腳本的url地址
然后找出代碼中聲明的依賴id,通過id來匹配正確的腳本url地址。這一部分的代碼在util-path.js
function id2Uri(id, refUri) { if (!id) return "" id = parseAlias(id) id = parsePaths(id) id = parseVars(id) id = normalize(id) var uri = addBase(id, refUri) uri = parseMap(uri) return uri }
這里有個特別的地方,類似require("a/b/c")這樣的寫法,seajs是如何知道腳本地址的絕對路徑的呢?道理很簡單,就是通過seajs自己往dom里添加的id為"seajsnode"的script節點或者是當前html中最后一個script節點,通過這些節點的src屬性獲取腳本的絕對路徑。
模塊加載過程讓我們把目光移回到核心的module.js中。seajs為模塊的加載過程定義了6種狀態。
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 }
也就是:
* FETCHING 開始加載當前模塊
* SAVED 當前模塊加載完成并保存模塊數據
* LOADING 開始加載依賴的模塊
* LOADED 依賴模塊已經加載完成
* EXECUTING 當前模塊執行中
* EXECUTED 當前模塊執行完成
其實這一加載執行過程并非線性的,當前模塊在加載所依賴的模塊的是,所依賴的模塊同樣也需要進行這一過程,直到所有的依賴都加載執行完畢,當前模塊才開始執行。
在module.js中seajs中的一些方法說明了上述整個流程。
Module.use 構造一個沒有factory的模塊,開始整個加載流程,狀態初始化為FETCHING到SAVED;
Module.prototype.load 通過load方法,開始加載子模塊,狀態由SAVED到LOADING;
Module.prototype.onload 當子模塊都加載完成后都會調用onload方法,狀態由LOADING到LOADED;
Module.prototype.exec 加載過程都結束了,開始執行模塊,狀態由EXECUTING到EXECUTED;
這里每個方法的詳細過程就不一一解析,有興趣的同學可以去看源碼。
實際上,seajs會對加載過的模塊保存一份引用在cachedMods中,在require的時候會先調用緩存中的模塊。
seajs.require = function(id) { var mod = Module.get(Module.resolve(id)) if (mod.status < STATUS.EXECUTING) { mod.onload() mod.exec() } return mod.exports } Module.get = function(uri, deps) { return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps)) }總結
前端模塊化一直是前端開發中比較重要的一點。前端開發相對其他語言來說比較特殊,尤其是對應大型Web項目的前端代碼,如何簡潔優雅地劃分模塊,如何管理這些模塊的依賴問題,這些都需要花一定的時間去認識和探討。因此,Common.js(致力于設計、規劃并標準化 JavaScript API)的誕生開啟了“ JavaScript 模塊化的時代”。前端領域的模塊化方案,像requireJS、SeaJS等都是Common.js的實踐者,對我們規劃前端的代碼很有幫助。然而,問題其實還有很多,seajs依然未能完全滿足前端模塊化開發,在性能問題、打包部署等方法還有著不足,不過技術的未來總在進步,相信以后會有更好的解決方法。
參考http://island205.github.io/HelloSea.js/
http://seajs.org/docs/#docs
http://chuansongme.com/account/wtp-notes
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/78062.html
摘要:這里的依賴都是通過來異步加載的,加載完畢之后立刻執行函數,在模塊文件執行完畢后包括和其他代碼,觸發的事件。 入口 seajs.use seajs.use直接調用Module.use(),Module.use的源碼如下: // Use function is equal to load a anonymous module // ids:模塊標識,uri是dirname + _us...
摘要:如果這個模塊的時候沒有設置,就表示是個匿名模塊,那怎么才能與之前發起請求的那個相匹配呢這里就有了一個全局變量,先將元數據放入這個對象。模塊加載完畢的回調保存元數據到匿名模塊,為請求的不管是不是匿名模塊,最后都是通過方法,將元數據存入到中。 近幾年前端工程化越來越完善,打包工具也已經是前端標配了,像seajs這種老古董早已停止維護,而且使用的人估計也幾個了。但這并不能阻止好奇的我,為了了...
摘要:所有依賴這個模塊的語句,都定義在一個回調函數中,等到所有依賴加載完成之后前置依賴,這個回調函數才會運行。如果將前面的代碼改寫成形式,就是下面這樣定義了一個文件,該文件依賴模塊,當模塊加載完畢之后執行回調函數,這里并沒有暴露任何變量。 模塊化是我們日常開發都要用到的基本技能,使用簡單且方便,但是很少人能說出來但是的原因及發展過程。現在通過對比不同時期的js的發展,將JavaScript模...
摘要:依賴信息是一個數組,比如上面的依賴數組是源碼如下是利用正則解析依賴的一個函數時間出發函數主要看這個部分注釋是防止拷貝該時間的回調函數,防止修改,困惑了一下。對的賦值需要同步執行,不能放在回調函數里。 sea.js想解決的問題 惱人的命名沖突 煩瑣的文件依賴 對應帶來的好處 Sea.js 帶來的兩大好處: 通過 exports 暴露接口。這意味著不需要命名空間了,更不需要全局變量。...
閱讀 4186·2023-04-26 02:40
閱讀 2667·2023-04-26 02:31
閱讀 2760·2021-11-15 18:08
閱讀 578·2021-11-12 10:36
閱讀 1438·2021-09-30 09:57
閱讀 5212·2021-09-22 15:31
閱讀 2640·2019-08-30 14:17
閱讀 1288·2019-08-30 12:58