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