seajs學(xué)習(xí)之模塊的依賴加載及模塊API的導(dǎo)出
前言
SeaJS非常強(qiáng)大,SeaJS可以加載任意 JavaScript 模塊和css模塊樣式,SeaJS會保證你在使用一個(gè)模塊時(shí),已經(jīng)將所依賴的其他模塊載入到腳本運(yùn)行環(huán)境中。
通過參照上文的demo,我們結(jié)合源碼分析在簡單的API調(diào)用的背后,到底使用了什么技巧來實(shí)現(xiàn)各個(gè)模塊的依賴加載以及模塊API的導(dǎo)出。
模塊類和狀態(tài)類
首先定義了一個(gè)Module類,對應(yīng)與一個(gè)模塊
function Module(uri, deps) { this.uri = uri this.dependencies = deps || [] this.exports = null this.status = 0 // Who depends on me this._waitings = {} // The number of unloaded dependencies this._remain = 0 }
Module有一些屬性,uri對應(yīng)該模塊的絕對url,在Module.define
函數(shù)中會有介紹;dependencies為依賴模塊數(shù)組;exports為導(dǎo)出的API;status為當(dāng)前的狀態(tài)碼;_waitings對象為當(dāng)前依賴該模塊的其他模塊哈希表,其中key為其他模塊的url;_remain為計(jì)數(shù)器,記錄還未加載的模塊個(gè)數(shù)。
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 }
上述為狀態(tài)對象,記錄模塊的當(dāng)前狀態(tài):模塊初始化狀態(tài)為0,當(dāng)加載該模塊時(shí),為狀態(tài)fetching;模塊加載完畢并且緩存在cacheMods后,為狀態(tài)saved;loading狀態(tài)意味著正在加載該模塊的其他依賴模塊;loaded表示所有依賴模塊加載完畢,執(zhí)行該模塊的回調(diào)函數(shù),并設(shè)置依賴該模塊的其他模塊是否還有依賴模塊未加載,若加載完畢執(zhí)行回調(diào)函數(shù);executing狀態(tài)表示該模塊正在執(zhí)行;executed則是執(zhí)行完畢,可以使用exports的API。
模塊的定義
commonJS規(guī)范規(guī)定用define
函數(shù)來定義一個(gè)模塊。define可以接受1,2,3個(gè)參數(shù)均可,不過對于Module/wrappings規(guī)范而言,module.declare
或者define
函數(shù)只能接受一個(gè)參數(shù),即工廠函數(shù)或者對象。不過原則上接受參數(shù)的個(gè)數(shù)并沒有本質(zhì)上的區(qū)別,只不過庫在后臺給額外添加模塊名。
seajs鼓勵使用define(function(require,exports,module){})
這種模塊定義方式,這是典型的Module/wrappings規(guī)范實(shí)現(xiàn)。但是在后臺通過解析工廠函數(shù)的require
方法來獲取依賴模塊并給模塊設(shè)置id和url。
// Define a module Module.define = function (id, deps, factory) { var argsLen = arguments.length // define(factory) if (argsLen === 1) { factory = id id = undefined } else if (argsLen === 2) { factory = deps // define(deps, factory) if (isArray(id)) { deps = id id = undefined } // define(id, factory) else { deps = undefined } } // Parse dependencies according to the module factory code // 如果deps為非數(shù)組,則序列化工廠函數(shù)獲取入?yún)ⅰ? if (!isArray(deps) && isFunction(factory)) { deps = parseDependencies(factory.toString()) } var meta = { id: id, uri: Module.resolve(id), // 絕對url deps: deps, factory: factory } // Try to derive uri in IE6-9 for anonymous modules // 導(dǎo)出匿名模塊的uri if (!meta.uri && doc.attachEvent) { var script = getCurrentScript() if (script) { meta.uri = script.src } // NOTE: If the id-deriving methods above is failed, then falls back // to use onload event to get the uri } // Emit `define` event, used in nocache plugin, seajs node version etc emit("define", meta) meta.uri ? Module.save(meta.uri, meta) : // Save information for "saving" work in the script onload event anonymousMeta = meta }
模塊定義的最后,通過Module.save
方法,將模塊保存到cachedMods緩存體中。
parseDependencies
方法比較巧妙的獲取依賴模塊。他通過函數(shù)的字符串表示,使用正則來獲取require(“…”)
中的模塊名。
var REQUIRE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g var SLASH_RE = /\\\\/g function parseDependencies(code) { var ret = [] // 此處使用函數(shù)序列化(傳入的factory)進(jìn)行字符串匹配,尋找require(“...”)的關(guān)鍵字 code.replace(SLASH_RE, "") .replace(REQUIRE_RE, function(m, m1, m2) { if (m2) { ret.push(m2) } }) return ret }
異步加載模塊
加載模塊可以有多種方式,xhr方式可以同步加載,也可以異步加載,但是存在同源問題,因此難以在此使用。另外script tag
方式在IE和現(xiàn)代瀏覽器下可以保證并行加載和順序執(zhí)行,script element
方式也可以保證并行加載但不保證順序執(zhí)行,因此這兩種方式都可以使用。
在seajs中,是采用script element
方式來并行加載js/css資源的,并針對舊版本的webkit瀏覽器加載css做了hack。
function request(url, callback, charset) { var isCSS = IS_CSS_RE.test(url) var node = doc.createElement(isCSS ? "link" : "script") if (charset) { var cs = isFunction(charset) ? charset(url) : charset if (cs) { node.charset = cs } } // 添加 onload 函數(shù)。 addOnload(node, callback, isCSS, url) if (isCSS) { node.rel = "stylesheet" node.href = url } else { node.async = true node.src = url } // For some cache cases in IE 6-8, the script executes IMMEDIATELY after // the end of the insert execution, so use `currentlyAddingScript` to // hold current node, for deriving url in `define` call currentlyAddingScript = node // ref: #185 & http://dev.jquery.com/ticket/2709 baseElement ? head.insertBefore(node, baseElement) : head.appendChild(node) currentlyAddingScript = null } function addOnload(node, callback, isCSS, url) { var supportOnload = "onload" in node // for Old WebKit and Old Firefox if (isCSS && (isOldWebKit || !supportOnload)) { setTimeout(function() { pollCss(node, callback) }, 1) // Begin after node insertion return } if (supportOnload) { node.onload = onload node.onerror = function() { emit("error", { uri: url, node: node }) onload() } } else { node.onreadystatechange = function() { if (/loaded|complete/.test(node.readyState)) { onload() } } } function onload() { // Ensure only run once and handle memory leak in IE node.onload = node.onerror = node.onreadystatechange = null // Remove the script to reduce memory leak if (!isCSS && !data.debug) { head.removeChild(node) } // Dereference the node node = null callback() } } // 針對 舊webkit和不支持onload的CSS節(jié)點(diǎn)判斷加載完畢的方法 function pollCss(node, callback) { var sheet = node.sheet var isLoaded // for WebKit < 536 if (isOldWebKit) { if (sheet) { isLoaded = true } } // for Firefox < 9.0 else if (sheet) { try { if (sheet.cssRules) { isLoaded = true } } catch (ex) { // The value of `ex.name` is changed from "NS_ERROR_DOM_SECURITY_ERR" // to "SecurityError" since Firefox 13.0. But Firefox is less than 9.0 // in here, So it is ok to just rely on "NS_ERROR_DOM_SECURITY_ERR" if (ex.name === "NS_ERROR_DOM_SECURITY_ERR") { isLoaded = true } } } setTimeout(function() { if (isLoaded) { // Place callback here to give time for style rendering callback() } else { pollCss(node, callback) } }, 20) }
其中有些細(xì)節(jié)還需注意,當(dāng)采用script element
方法插入script節(jié)點(diǎn)時(shí),盡量作為首個(gè)子節(jié)點(diǎn)插入到head中,這是由于一個(gè)難以發(fā)現(xiàn)的bug:
GLOBALEVAL WORKS INCORRECTLY IN IE6 IF THE CURRENT PAGE HAS <BASE HREF> TAG IN THE HEAD
fetch模塊
初始化Module對象時(shí),狀態(tài)為0,該對象對應(yīng)的js文件并未加載,若要加載js文件,需要使用上節(jié)提到的request
方法,但是也不可能僅僅加載該文件,還需要設(shè)置module對象的狀態(tài)及其加載module依賴的其他模塊。
這些邏輯在fetch
方法中得以體現(xiàn):
// Fetch a module // 加載該模塊,fetch函數(shù)中調(diào)用了seajs.request函數(shù) Module.prototype.fetch = function(requestCache) { var mod = this var uri = mod.uri mod.status = STATUS.FETCHING // Emit `fetch` event for plugins such as combo plugin var emitData = { uri: uri } emit("fetch", emitData) var requestUri = emitData.requestUri || uri // Empty uri or a non-CMD module if (!requestUri || fetchedList[requestUri]) { mod.load() return } if (fetchingList[requestUri]) { callbackList[requestUri].push(mod) return } fetchingList[requestUri] = true callbackList[requestUri] = [mod] // Emit `request` event for plugins such as text plugin emit("request", emitData = { uri: uri, requestUri: requestUri, onRequest: onRequest, charset: data.charset }) if (!emitData.requested) { requestCache ? requestCache[emitData.requestUri] = sendRequest : sendRequest() } function sendRequest() { seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset) } // 回調(diào)函數(shù) function onRequest() { delete fetchingList[requestUri] fetchedList[requestUri] = true // Save meta data of anonymous module if (anonymousMeta) { Module.save(uri, anonymousMeta) anonymousMeta = null } // Call callbacks var m, mods = callbackList[requestUri] delete callbackList[requestUri] while ((m = mods.shift())) m.load() } }
其中seajs.request
就是上節(jié)的request
方法。onRequest
作為回調(diào)函數(shù),作用是加載該模塊的其他依賴模塊。
總結(jié)
以上就是seajs模塊的依賴加載及模塊API的導(dǎo)出的全部內(nèi)容了,小編會在下一節(jié),將介紹模塊之間依賴的加載以及模塊的執(zhí)行。感興趣的朋友們可以繼續(xù)關(guān)注腳本之家。
相關(guān)文章
seajs學(xué)習(xí)之模塊的依賴加載及模塊API的導(dǎo)出
SeaJS是一個(gè)遵循 CommonJS 規(guī)范的模塊加載框架,可用來輕松愉悅地加載任意JavaScript模塊和css模塊樣式。SeaJS接口和方法也非常少,SeaJS 就兩個(gè)核心:模塊定義和模塊的加載及依賴關(guān)系。本文將詳細(xì)介紹模塊的依賴加載及模塊API的導(dǎo)出,有需要的朋友們可以參考借鑒。2016-10-10詳解Sea.js中Module.exports和exports的區(qū)別
最近在看Seajs時(shí),看到了exports.doSomething和module.exports,想對這兩者的區(qū)別一探究竟。所以下面這篇文章主要介紹了Sea.js中Module.exports和exports的區(qū)別,需要的朋友可以參考借鑒,一起來看看吧。2017-02-02