179人参与 • 2024-05-15 • Seajs
前言
seajs非常强大,seajs可以加载任意 javascript 模块和css模块样式,seajs会保证你在使用一个模块时,已经将所依赖的其他模块载入到脚本运行环境中。
通过参照的demo,我们结合源码分析在简单的api调用的背后,到底使用了什么技巧来实现各个模块的依赖加载以及模块api的导出。
模块类和状态类
首先定义了一个module类,对应与一个模块
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对应该模块的绝对url,在module.define
函数中会有介绍;dependencies为依赖模块数组;exports为导出的api;status为当前的状态码;_waitings对象为当前依赖该模块的其他模块哈希表,其中key为其他模块的url;_remain为计数器,记录还未加载的模块个数。
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 }
上述为状态对象,记录模块的当前状态:模块初始化状态为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 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为非数组,则序列化工厂函数获取入参。 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(“…”)
中的模块名。
var require_re = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\s\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g var slash_re = /\\\\/g 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。
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 函数。 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节点判断加载完毕的方法 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 <base href> tag in the head
fetch模块
初始化module对象时,状态为0,该对象对应的js文件并未加载,若要加载js文件,需要使用上节提到的request
方法,但是也不可能仅仅加载该文件,还需要设置module对象的状态及其加载module依赖的其他模块。
这些逻辑在fetch
方法中得以体现:
// fetch a 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的导出的全部内容了,小编会在下一节,将介绍模块之间依赖的加载以及模块的执行。感兴趣的朋友们可以继续关注代码网。
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论