大黑 2019-06-26
Seajs是一款模块化开发框架,遵循CMD规范。虽然到现在为止很多模块打包工具比它更加的完善,但还是有必要拜读一下的,毕竟为前端模块化的发展做了很大的贡献,分析一下涨涨姿势。
文章主要从以下几个方面来分析。有不对的地方,欢迎大家指出。
CMD(Common Module Definition)是seajs在推广中规范出来的。详情请看 CMD模块定义规范
先在浏览器控制台中打印下seajs这个全局变量,可以看到它挂载的一些对象和方法。让我们先对它有个整体的感受
seajs 从use函数开始,到加载module的过程大致如下:
模块加载完后浏览器会立马执行define函数,这个函数比较有意思,先看源代码
// Resolve id to uri
Module.resolve = function(id, refUri) {
// Emit `resolve` event for plugins such as text plugin
var emitData = { id: id, refUri: refUri }
emit("resolve", emitData)
return emitData.uri || seajs.resolve(emitData.id, refUri)
}
// 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
if (!isArray(deps) && isFunction(factory)) {
deps = parseDependencies(factory.toString())
}
var meta = {
id: id,
uri: Module.resolve(id),
deps: deps,
factory: factory
}
// Try to derive uri in IE6-9 for anonymous modules
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
}有意思的是如果factory是个函数,同时deps不是一个数组,那么会将factory序列化,然后通过正则匹配从其中解析出依赖
var REQUIRE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g
var SLASH_RE = /\\\\/g
function parseDependencies(code) {
var ret = []
code.replace(SLASH_RE, "")
.replace(REQUIRE_RE, function(m, m1, m2) {
if (m2) {
ret.push(m2)
}
})
return ret
}解析依赖后会扔到模块缓存系统中。之前模块加载的时候就可以看到,主模块加载完之后会执行factory函数。在执行factory函数的时候,会执行require加载的依赖。而require函数会判断模块状态是否已经执行过了,如果不是那么就加载依赖,加载完后执行依赖。最后将执行的结果暴露给exports对象。
到此,seajs的整个加载执行过程已经分析完毕。相比requirejs,seajs代码不是很多,但是能够感受到代码组织起来的精妙之处。
seajs解决模块的互相依赖是通过缓存系统和模块的状态来实现的。
// `onload` event is not supported in WebKit < 535.23 and Firefox < 9.0
// ref:
// - https://bugs.webkit.org/show_activity.cgi?id=38995
// - https://bugzilla.mozilla.org/show_bug.cgi?id=185236
// - https://developer.mozilla.org/en/HTML/Element/link#Stylesheet_load_events
var isOldWebKit = +navigator.userAgent
.replace(/.*(?:AppleWebKit|AndroidWebKit)\/(\d+).*/, "$1") < 536解决的办法是通过一个定时器,去监听link节点是否已经加载,并且解析完。
function getCurrentScript() {
if (currentlyAddingScript) {
return currentlyAddingScript
}
// For IE6-9 browsers, the script onload event may not fire right
// after the script is evaluated. Kris Zyp found that it
// could query the script nodes and the one that is in "interactive"
// mode indicates the current script
// ref: http://goo.gl/JHfFW
if (interactiveScript && interactiveScript.readyState === "interactive") {
return interactiveScript
}
var scripts = head.getElementsByTagName("script")
for (var i = scripts.length - 1; i >= 0; i--) {
var script = scripts[i]
if (script.readyState === "interactive") {
interactiveScript = script
return interactiveScript
}
}
}解决办法是通过脚本的readyState===‘interactive’来判断
以上就是对seajs的一个大致的分析,如有错误,欢迎指出。