seajs学习之模块的依赖加载及模块API的导出

前端之家收集整理的这篇文章主要介绍了seajs学习之模块的依赖加载及模块API的导出前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

前言

SeaJS非常强大,SeaJS可以加载任意 JavaScript 模块和css模块样式,SeaJS会保证你在使用一个模块时,已经将所依赖的其他模块载入到脚本运行环境中。

通过参照的demo,我们结合源码分析在简单的API调用的背后,到底使用了什么技巧来实现各个模块的依赖加载以及模块API的导出。

模块类和状态类

首先定义了一个Module类,对应与一个模块

// Who depends on me
this._waitings = {}

// The number of unloaded dependencies
this._remain = 0
}

Module有一些属性,uri对应该模块的绝对url,在Module.define函数中会有介绍;dependencies为依赖模块数组;exports为导出的API;status为当前的状态码;_waitings对象为当前依赖该模块的其他模块哈希表,其中key为其他模块的url;_remain为计数器,记录还未加载的模块个数。

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 }

上述为状态对象,记录模块的当前状态:模块初始化状态为0,当加载该模块时,为状态fetching;模块加载完毕并且缓存在cacheMods后,为状态saved;loading状态意味着正在加载该模块的其他依赖模块;loaded表示所有依赖模块加载完毕,执行该模块的回调函数,并设置依赖该模块的其他模块是否还有依赖模块未加载,若加载完毕执行回调函数;executing状态表示该模块正在执行;executed则是执行完毕,可以使用exports的API。

模块的定义

commonJS规范规定用define函数来定义一个模块。define可以接受1,2,3个参数均可,不过对于Module/wrappings规范而言,module.declare或者define函数只能接受一个参数,即工厂函数或者对象。不过原则上接受参数的个数并没有本质上的区别,只不过库在后台给额外添加模块名。

seajs鼓励使用define(function(require,exports,module){})这种模块定义方式,这是典型的Module/wrappings规范实现。但是在后台通过解析工厂函数require方法获取依赖模块并给模块设置id和url。

// 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为非数组,则序列化工厂函数获取入参。
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
// 导出匿名模块的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方法比较巧妙的获取依赖模块。他通过函数的字符串表示,使用正则来获取require(“…”)中的模块名。

function parseDependencies(code) {
var ret = []
// 此处使用函数序列化(传入的factory)进行字符串匹配,寻找require(“...”)的关键字
code.replace(SLASH_RE,"")
.replace(REQUIRE_RE,function(m,m1,m2) {
if (m2) {
ret.push(m2)
}
})

return ret
}

异步加载模块

加载模块可以有多种方式,xhr方式可以同步加载,也可以异步加载,但是存在同源问题,因此难以在此使用。另外script tag方式在IE和现代浏览器下可以保证并行加载和顺序执行,script element方式也可以保证并行加载但不保证顺序执行,因此这两种方式都可以使用。

在seajs中,是采用script element方式来并行加载js/css资源的,并针对旧版本的webkit浏览器加载css做了hack。

if (charset) {
var cs = isFunction(charset) ? charset(url) : charset
if (cs) {
node.charset = cs
}
}

// 添加 onload 函数
addOnload(node,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,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节点判断加载完毕的方法
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)
}

其中有些细节还需注意,当采用script element方法插入script节点时,尽量作为首个子节点插入到head中,这是由于一个难以发现的bug:

GLOBALEVAL WORKS INCORRECTLY IN IE6 IF THE CURRENT PAGE HAS TAG IN THE HEAD

fetch模块

初始化Module对象时,状态为0,该对象对应的js文件并未加载,若要加载js文件,需要使用上节提到的request方法,但是也不可能仅仅加载该文件,还需要设置module对象的状态及其加载module依赖的其他模块。

这些逻辑在fetch方法中得以体现:

函数中调用了seajs.request函数 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)
}
// 回调函数
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就是上节的request方法onRequest作为回调函数,作用是加载该模块的其他依赖模块。

总结

以上就是seajs模块的依赖加载及模块API的导出的全部内容了,小编会在下一节,将介绍模块之间依赖的加载以及模块的执行。感兴趣的朋友们可以继续关注编程之家。

猜你在找的JavaScript相关文章