seajs与requirejs的不同是,define(deps,function(){})中依赖用于定义模块回调函数执行的时机为等依赖加载完成后,回调中传入的参数也是require、exports、mod,实现依赖内置;requirejs中,deps取得依赖模块exports后传入回调函数function中作为参数,依赖模块必须在define语句中约定。
1.util-events使用发布-订阅模式书写简单的事件系统,seajs内部触发及绑点事件使用
// 简单的事件系统,提供on监听事件、off移除事件、emit触发事件方法 var events = data.events = {} seajs.on = function(name, callback) { var list = events[name] || (events[name] = []) list.push(callback) return seajs } seajs.off = function(name, callback) { if (!(name || callback)) { events = data.events = {} return seajs } var list = events[name] if (list) { if (callback) { for (var i = list.length - 1; i >= 0; i--) { if (list[i] === callback) { list.splice(i, 1) } } } else { delete events[name] } } return seajs } var emit = seajs.emit = function(name, data) { var list = events[name] if (list) { list = list.slice() for(var i = 0, len = list.length; i < len; i++) { list[i](data) } } return seajs }
2.util-path解析路径
// 获取sea.js脚本加载的路径loaderDir、data.cwd,以及解析模块路径的方法seajs.resolve var DIRNAME_RE = /[^?#]*\// // 文件目录路径不带url的查询字符串和hash值,且以"/"结尾 var DOT_RE = /\/\.\//g var DOUBLE_DOT_RE = /\/[^/]+\/\.\.\// var MULTI_SLASH_RE = /([^:/])\/+\//g // 获取文件目录 function dirname(path) { return path.match(DIRNAME_RE)[0] } // 相对路径转化为绝对路径 function realpath(path) { // /a/b/./c/./d ==> /a/b/c/d path = path.replace(DOT_RE, "/") // a///b/c ==> a/b/c path = path.replace(MULTI_SLASH_RE, "$1/") // a/b/c/../../d ==> a/b/../d ==> a/d while (path.match(DOUBLE_DOT_RE)) { path = path.replace(DOUBLE_DOT_RE, "/") } return path } // 移除“#”、添加".js" function normalize(path) { var last = path.length - 1 var lastC = path.charCodeAt(last) // If the uri ends with `#`, just return it without '#' if (lastC === 35 /* "#" */) { return path.substring(0, last) } return (path.substring(last - 2) === ".js" || path.indexOf("?") > 0 || lastC === 47 /* "/" */) ? path : path + ".js" } var PATHS_RE = /^([^/:]+)(\/.+)$/ var VARS_RE = /{([^{]+)}/g // 模块别名替换 function parseAlias(id) { var alias = data.alias return alias && isString(alias[id]) ? alias[id] : id } // 模块路径替换 function parsePaths(id) { var paths = data.paths var m if (paths && (m = id.match(PATHS_RE)) && isString(paths[m[1]])) { id = paths[m[1]] + m[2] } return id } // 模块变量替换 function parseVars(id) { var vars = data.vars if (vars && id.indexOf("{") > -1) { id = id.replace(VARS_RE, function(m, key) { return isString(vars[key]) ? vars[key] : m }) } return id } // map映射替换,map为数组形式的函数或包含两项元素的数组 function parseMap(uri) { var map = data.map var ret = uri if (map) { for (var i = 0, len = map.length; i < len; i++) { var rule = map[i] ret = isFunction(rule) ? (rule(uri) || uri) : uri.replace(rule[0], rule[1]) if (ret !== uri) break } } return ret } var ABSOLUTE_RE = /^\/\/.|:\// // 绝对路径以//或:/起始 var ROOT_DIR_RE = /^.*?\/\/.*?\// // url匹配时为协议名http(s):,加上域名www.baidu.com function addBase(id, refUri) { var ret var first = id.charCodeAt(0) // 绝对路径 if (ABSOLUTE_RE.test(id)) { ret = id } // "."起始的相对路径,相对dirname(refUri)或sea.js加载的文件路径 else if (first === 46 /* "." */) { ret = (refUri ? dirname(refUri) : data.cwd) + id } // "/"起始的相对路径,当前域名下的根路径 else if (first === 47 /* "/" */) { var m = data.cwd.match(ROOT_DIR_RE) ret = m ? m[0] + id.substring(1) : id } // 以data.base为基准,设置模块的路径 else { ret = data.base + id } // 加上域名 if (ret.indexOf("//") === 0) { ret = location.protocol + ret } // 转化为绝对路径 return realpath(ret) } // 模块路径解析 function id2Uri(id, refUri) { if (!id) return "" id = parseAlias(id) id = parsePaths(id) id = parseAlias(id) id = parseVars(id) id = parseAlias(id) id = normalize(id) id = parseAlias(id) var uri = addBase(id, refUri) uri = parseAlias(uri) uri = parseMap(uri) return uri } // 模块路径解析 seajs.resolve = id2Uri // Check environment var isWebWorker = typeof window === 'undefined' && typeof importScripts !== 'undefined' && isFunction(importScripts) // Ignore about:xxx and blob:xxx var IGNORE_LOCATION_RE = /^(about|blob):/ // Sea.js文件所在目录url var loaderDir // Sea.js文件加载时的url,加载sea.js的script标签最好添加id为seajsnode var loaderPath // 当前页面的文件目录url var cwd = (!location.href || IGNORE_LOCATION_RE.test(location.href)) ? '' : dirname(location.href) if (isWebWorker) { // Web worker不创建节点,通过Error对象的stack获取url,stack属性第一行为'Error'文本 var stack try { var up = new Error() throw up } catch (e) { stack = e.stack.split('\n') } stack.shift() var m // Try match `url:row:col` from stack trace line. Known formats: // Chrome: ' at http://localhost:8000/script/sea-worker-debug.js:294:25' // FireFox: '@http://localhost:8000/script/sea-worker-debug.js:1082:1' // IE11: ' at Anonymous function (http://localhost:8000/script/sea-worker-debug.js:295:5)' // Don't care about older browsers since web worker is an HTML5 feature var TRACE_RE = /.*?((?:http|https|file)(?::\/{2}[\w]+)(?:[\/|\.]?)(?:[^\s"]*)).*?/i // Try match `url` (Note: in IE there will be a tailing ')') var URL_RE = /(.*?):\d+:\d+\)?$/ // Find url of from stack trace. // Cannot simply read the first one because sometimes we will get: // Error // at Error (native) <- Here's your problem // at http://localhost:8000/_site/dist/sea.js:2:4334 <- What we want // at http://localhost:8000/_site/dist/sea.js:2:8386 // at http://localhost:8000/_site/tests/specs/web-worker/worker.js:3:1 while (stack.length > 0) { var top = stack.shift() m = TRACE_RE.exec(top) if (m != null) { break } } var url if (m != null) { // Remove line number and column number // No need to check, can't be wrong at this point var url = URL_RE.exec(m[1])[1] } loaderPath = url loaderDir = dirname(url || cwd) // This happens with inline worker. // When entrance script's location.href is a blob url, // cwd will not be available. // Fall back to loaderDir. if (cwd === '') { cwd = loaderDir } } else { var doc = document var scripts = doc.scripts var loaderScript = doc.getElementById("seajsnode") || scripts[scripts.length - 1] function getScriptAbsoluteSrc(node) { // 针对IE6、7的兼容性问题 http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx return node.hasAttribute ? node.src : node.getAttribute("src", 4) } loaderPath = getScriptAbsoluteSrc(loaderScript) loaderDir = dirname(loaderPath || cwd) }
3.util-cs获取当前的script节点
// 获取当前的script节点 var interactiveScript function getCurrentScript() { // 非webworker环境通过创建script节点添加js脚本,currentlyAddingScript引用该script节点 if (currentlyAddingScript) { return currentlyAddingScript } if (interactiveScript && interactiveScript.readyState === "interactive") { return interactiveScript } var scripts = head.getElementsByTagName("script") // 从尾端获取第一个readyState状态为"interactive"(避免ie6-9的onload事件不触发)的script for (var i = scripts.length - 1; i >= 0; i--) { var script = scripts[i] if (script.readyState === "interactive") { interactiveScript = script return interactiveScript } } }
4.util-request创建script节点或者importScripts加载脚本
// 通过importScripts函数或创建script节点的方式添加js脚本 // webworker通过importScripts添加js脚本 if (isWebWorker) { function requestFromWebWorker(url, callback, charset, crossorigin) { var error try { importScripts(url) } catch (e) { error = e } callback(error) } seajs.request = requestFromWebWorker // 文档元素document下添加script节点,该节点绑定onload事件,必要时腾出内存空间 }else { var doc = document var head = doc.head || doc.getElementsByTagName("head")[0] || doc.documentElement var baseElement = head.getElementsByTagName("base")[0] var currentlyAddingScript function request(url, callback, charset, crossorigin) { var node = doc.createElement("script") if (charset) { node.charset = charset } if (!isUndefined(crossorigin)) { node.setAttribute("crossorigin", crossorigin) } addOnload(node, callback, url) 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, url) { var supportOnload = "onload" in node if (supportOnload) { node.onload = onload node.onerror = function() { emit("error", { uri: url, node: node }) onload(true) } } else { node.onreadystatechange = function() { if (/loaded|complete/.test(node.readyState)) { onload() } } } function onload(error) { node.onload = node.onerror = node.onreadystatechange = null// 针对IE的兼容性问题,确保脚本只执行一次 if (!data.debug) { head.removeChild(node)// 移除节点,节省内存空间,脚本已执行 } node = null callback(error) } } seajs.request = request }
5.util-lang类型判断、标识符生成
// 类型判断、标识符生成 function isType(type) { return function(obj) { return {}.toString.call(obj) == "[object " + type + "]" } } var isObject = isType("Object") var isString = isType("String") var isArray = Array.isArray || isType("Array") var isFunction = isType("Function") var isUndefined = isType("Undefined") var _cid = 0 function cid() { return _cid++ }
6.util-deps解析function函数体内使用require加载的依赖模块
// 通过解析function函数体内的字符串,获取依赖模块 function parseDependencies(s) { if(s.indexOf('require') == -1) { return [] } var index = 0, peek, length = s.length, isReg = 1, modName = 0, res = [] var parentheseState = 0, parentheseStack = [] var braceState, braceStack = [], isReturn while(index < length) { readch()// 读取字符串 // 通过isReturn判断空格前为return,或者空格为'\n'、'\r',isReturn计为0 if(isBlank()) { if(isReturn && (peek == '\n' || peek == '\r')) { braceState = 0 isReturn = 0 } // 使用modName存在require语句,获取require引号包裹的内容,添加到res中,modName重置为0 }else if(isQuote()) { dealQuote() isReg = 1 isReturn = 0 braceState = 0 // index跳转到正则、单行注释、多行注释后 }else if(peek == '/') { readch() // 单行注释index跳转到行尾,或文本末梢 if(peek == '/') { index = s.indexOf('\n', index) if(index == -1) { index = s.length } // 多行注释index跳转到文本末梢,或者"*/"后 }else if(peek == '*') { var i = s.indexOf('\n', index) index = s.indexOf('*/', index) if(index == -1) { index = length }else { index += 2 } if(isReturn && i != -1 && i < index) { braceState = 0 isReturn = 0 } // index跳转到正则表达式后 }else if(isReg) { dealReg() isReg = 0 isReturn = 0 braceState = 0 // 设置isReg为1,index跳转到'/'前,重新读取字符串,进入isReg条件语句分支 }else { index-- isReg = 1 isReturn = 0 braceState = 1 } // 分析获取modName,或者index跳到空格前 }else if(isWord()) { dealWord() // index跳转到数值后 }else if(isNumber()) { dealNumber() isReturn = 0 braceState = 0 // 示例为if等语句后")"支持正则表达式书写 }else if(peek == '(') { parentheseStack.push(parentheseState) isReg = 1 isReturn = 0 braceState = 1 }else if(peek == ')') { isReg = parentheseStack.pop() isReturn = 0 braceState = 0 // 示例为“}”后instanceof、delete语句后不支持正则书写 }else if(peek == '{') { if(isReturn) { braceState = 1 } braceStack.push(braceState) isReturn = 0 isReg = 1 }else if(peek == '}') { braceState = braceStack.pop() isReg = !braceState isReturn = 0 }else { var next = s.charAt(index) if(peek == ';') { braceState = 0 }else if(peek == '-' && next == '-' || peek == '+' && next == '+' || peek == '=' && next == '>') { braceState = 0 index++ }else { braceState = 1 } isReg = peek != ']' isReturn = 0 } } return res // 逐个字符读取 function readch() { peek = s.charAt(index++) } // 是否空格 function isBlank() { return /\s/.test(peek) } // 是否引号 function isQuote() { return peek == '"' || peek == "'" } // 使用modName存在require语句,获取require引号包裹的内容,添加到res中,modName重置为0 function dealQuote() { var start = index var c = peek var end = s.indexOf(c, start) // 未找到闭合的引号 if(end == -1) { index = length // 引号前不是\\ }else if(s.charAt(end - 1) != '\\') { index = end + 1 // 引号前是\\,找到闭合的引号 }else { while(index < length) { readch() if(peek == '\\') { index++ }else if(peek == c) { break } } } if(modName) { res.push(s.substring(start, index - 1)) modName = 0 } } // index跳转到正则表达式后 function dealReg() { index-- while(index < length) { readch() if(peek == '\\') { index++ }else if(peek == '/') { break }else if(peek == '[') { while(index < length) { readch() if(peek == '\\') { index++ }else if(peek == ']') { break } } } } } // 是否字符串 function isWord() { return /[a-z_$]/i.test(peek) } // 分析获取modName,或者index跳到空格前 function dealWord() { var s2 = s.slice(index - 1)// 以当前字符串起始截取字符串 var r = /^[\w$]+/.exec(s2)[0]// 匹配字符串或数字或下划线 parentheseState = { 'if': 1, 'for': 1, 'while': 1, 'with': 1 }[r] isReg = { 'break': 1, 'case': 1, 'continue': 1, 'debugger': 1, 'delete': 1, 'do': 1, 'else': 1, 'false': 1, 'if': 1, 'in': 1, 'instanceof': 1, 'return': 1, 'typeof': 1, 'void': 1 }[r] isReturn = r == 'return' braceState = { 'instanceof': 1, 'delete': 1, 'void': 1, 'typeof': 1, 'return': 1 }.hasOwnProperty(r) modName = /^require\s*(?:\/\*[\s\S]*?\*\/\s*)?\(\s*(['"]).+?\1\s*[),]/.test(s2) // 若该字符串后紧跟require语句,index跳到require后引号位置 if(modName) { r = /^require\s*(?:\/\*[\s\S]*?\*\/\s*)?\(\s*['"]/.exec(s2)[0] index += r.length - 2 // 若该字符串后不是require语句,index跳到空格位置 }else { index += /^[\w$]+(?:\s*\.\s*[\w$]+)*/.exec(s2)[0].length - 1 } } // 判断字符串是否数字或者'.'+数字 function isNumber() { return /\d/.test(peek) || peek == '.' && /\d/.test(s.charAt(index)) } // index跳转到数值后 function dealNumber() { var s2 = s.slice(index - 1) var r if(peek == '.') { r = /^\.\d+(?:E[+-]?\d*)?\s*/i.exec(s2)[0] }else if(/^0x[\da-f]*/i.test(s2)) { r = /^0x[\da-f]*\s*/i.exec(s2)[0] }else { r = /^\d+\.?\d*(?:E[+-]?\d*)?\s*/i.exec(s2)[0] } index += r.length - 1 isReg = 0 } }
7.module创建模块、约定define、seajs.use方法
// 缓存加载的模块,Module.get方法创建模块时添加;添加完成后通过Module.get方法获取 var cachedMods = seajs.cache = {} // define方法定义的模块加载过程中缓存该模块的id、url和依赖 var anonymousMeta // 缓存加载中js文件模块信息 var fetchingList = {} // 缓存加载成功的js文件模块信息 var fetchedList = {} // 依赖模块加载过程中以数组形式缓存该模块,加载完成后通过回调函数清除缓存信息 var callbackList = {} var STATUS = Module.STATUS = { FETCHING: 1,// 模块通过创建script节点或importScripts函数加载过程中 SAVED: 2,// define方法定义的模块,模块信息id、dependenciess等已存入mod的属性 LOADING: 3,// 当前模块已加载成功,等待依赖模块加载过程中 LOADED: 4,// 当前模块及其依赖均载入完成,当前模块的回调可被执行 EXECUTING: 5,// define定义的模块已加载,但尚未获取到mod.exports;不符合cmd规范编写的模块载入时即执行 EXECUTED: 6,// 当前模块的回调已执行,mod.exports存在的前提下将获取mod.exports ERROR: 7// 404找不到模块 } function Module(uri, deps) { this.uri = uri// 模块的绝对路径 this.dependencies = deps || []// 依赖模块的id this.deps = {} // 以依赖模块的id为键,依赖模块的引用为值 this.status = 0// 模块的加载状态等 this._entry = []// 初始化设置当前模块,pass方法执行后更新为被依赖模块,用于获取执行被依赖模块的回调函数 } // 依赖路径解析 Module.prototype.resolve = function() { var mod = this var ids = mod.dependencies var uris = [] for (var i = 0, len = ids.length; i < len; i++) { uris[i] = Module.resolve(ids[i], mod.uri) } return uris } // 依赖模块的_entry属性更新为当前模块,即被依赖模块 // 意义是通过load方法调用onload方法,当依赖模块加载完成后,执行被依赖模块的回调函数 Module.prototype.pass = function() { var mod = this var len = mod.dependencies.length for (var i = 0; i < mod._entry.length; i++) { var entry = mod._entry[i]// 被依赖的模块 var count = 0 for (var j = 0; j < len; j++) { var m = mod.deps[mod.dependencies[j]]// 依赖模块 // 依赖模块尚未加载,依赖模块的_entry属性添加当前模块,count计数加1 if (m.status < STATUS.LOADED && !entry.history.hasOwnProperty(m.uri)) { entry.history[m.uri] = true count++ m._entry.push(entry) if(m.status === STATUS.LOADING) { m.pass() } } } if (count > 0) { entry.remain += count - 1// 被依赖的模块尚未加载的依赖模块数量 mod._entry.shift() i-- } } } // 调用fetch方法加载依赖模块,或者当依赖模块加载完成后,onload事件触发时,执行被依赖模块的回调 Module.prototype.load = function() { var mod = this if (mod.status >= STATUS.LOADING) { return } mod.status = STATUS.LOADING // resovle方法用于获取依赖模块的路径,数组形式 var uris = mod.resolve() emit("load", uris) // 获取或创建依赖模块,但不加载依赖模块 for (var i = 0, len = uris.length; i < len; i++) { mod.deps[mod.dependencies[i]] = Module.get(uris[i]) } // 依赖模块的_entry属性存储当前模块;通过load方法调用onload方法,当依赖模块加载完成后,执行被依赖模块的回调函数 mod.pass() // pass方法执行完成后,mod._entry存储被依赖的模块;当前模块加载完成时,执行被依赖模块的回调函数 // 执行时机为,当依赖模块加载完成后,onload事件发生时,调用该依赖模块的load方法,执行被依赖模块的回调 if (mod._entry.length) { mod.onload() return } // 缓存依赖模块的加载方法,内部调用seajs.request,创建script节点或执行importScripts函数 var requestCache = {} var m for (i = 0; i < len; i++) { m = cachedMods[uris[i]] if (m.status < STATUS.FETCHING) { // 创建script节点,加载依赖模块的函数存入requestCache中,调用requestCache[requestUri]()正式加载依赖模块 m.fetch(requestCache) } else if (m.status === STATUS.SAVED) { // define方法定义的模块通过script提前载入时,调用load方法执行被依赖模块的回调 m.load() } } // 因IE6-9的兼容性问题, Issues#808,一次性创建script节点或importScripts函数添加js脚本 for (var requestUri in requestCache) { if (requestCache.hasOwnProperty(requestUri)) { requestCache[requestUri]() } } } // pass方法执行完成后,mod._entry存储依赖当前模块的模块,加载完成时执行所有被依赖模块的回调函数 // 并且mod.callback仅当模块使用seajs.use方法书写时存在 // define方法定义的模块,其回调函数存入mod.factory Module.prototype.onload = function() { var mod = this mod.status = STATUS.LOADED for (var i = 0, len = (mod._entry || []).length; i < len; i++) { var entry = mod._entry[i] if (--entry.remain === 0) { entry.callback() } } delete mod._entry } // 404加载失败时认为依赖模块尚未完成加载,调用mod.onload()执行被依赖模块的回调 Module.prototype.error = function() { var mod = this mod.onload() mod.status = STATUS.ERROR } // define方法书写的模块,含有mod.factory方法,exec方法向回调中提供参数require、exports、mod,实现依赖内置 // 供seajs.require、seajs.use方法调用模块时,作为依赖模块 Module.prototype.exec = function () { var mod = this if (mod.status >= STATUS.EXECUTING) { return mod.exports } mod.status = STATUS.EXECUTING if (mod._entry && !mod._entry.length) { delete mod._entry } // seajs.define方法书写的模块不符合cmd规范 if (!mod.hasOwnProperty('factory')) { mod.non = true return } var uri = mod.uri function require(id) { var m = mod.deps[id] || Module.get(require.resolve(id)) if (m.status == STATUS.ERROR) { throw new Error('module was broken: ' + m.uri) } return m.exec() } require.resolve = function(id) { return Module.resolve(id, uri) } require.async = function(ids, callback) { Module.use(ids, callback, uri + "_async_" + cid()) return require } var factory = mod.factory var exports = isFunction(factory) ? factory.call(mod.exports = {}, require, mod.exports, mod) : factory if (exports === undefined) { exports = mod.exports } delete mod.factory mod.exports = exports mod.status = STATUS.EXECUTED emit("exec", mod) return mod.exports } // 创建script节点或importScripts函数加载模块;模块加载完成后调用load方法,执行被依赖模块的回调函数 Module.prototype.fetch = function(requestCache) { var mod = this var uri = mod.uri mod.status = STATUS.FETCHING var emitData = { uri: uri } emit("fetch", emitData)// combo插件使用事件 var requestUri = emitData.requestUri || uri if (!requestUri || fetchedList.hasOwnProperty(requestUri)) { mod.load() return } if (fetchingList.hasOwnProperty(requestUri)) { callbackList[requestUri].push(mod) return } fetchingList[requestUri] = true callbackList[requestUri] = [mod] // data.charset、data.crossorigin通过seajs.conifg设置,data作为seajs的闭包缓存 emit("request", emitData = {// text插件使用事件 uri: uri, requestUri: requestUri, onRequest: onRequest, charset: isFunction(data.charset) ? data.charset(requestUri) : data.charset, crossorigin: isFunction(data.crossorigin) ? data.crossorigin(requestUri) : data.crossorigin }) if (!emitData.requested) { requestCache ? requestCache[emitData.requestUri] = sendRequest : sendRequest() } // 浏览器环境通过创建script节点的方式获取模块,webworker通过importScripts函数获取模块 function sendRequest() { seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset, emitData.crossorigin) } // script节点onload事件触发时的回调函数 function onRequest(error) { delete fetchingList[requestUri] fetchedList[requestUri] = true // 当加载的js文件含define方法定义的模块且定义了id,通过save方法存储模块的id和依赖,改变模块的状态 if (anonymousMeta) { Module.save(uri, anonymousMeta) anonymousMeta = null } var m, mods = callbackList[requestUri] delete callbackList[requestUri] while ((m = mods.shift())) { // When 404 occurs, the params error will be true if(error === true) { m.error() } else { // m作为依赖模块,load方法执行时,调用onload方法执行被依赖模块的回调函数 m.load() } } } } // 将模块的id转化为url输出 Module.resolve = function(id, refUri) { var emitData = { id: id, refUri: refUri } emit("resolve", emitData)// text插件等使用事件 return emitData.uri || seajs.resolve(emitData.id, refUri) } // 定义一个模块,作为依赖,通过其他模块加载 // define中定义deps的目的不像requirejs为回调factory提供参数,而是约定回调factory在依赖加载完成后执行 // 被依赖模块间接调用exec方法向依赖模块的回调中注入require、exports、mod参数,实现依赖内置 // 示例 define(["jquey"],function(require,exports,mod){ }); Module.define = function (id, deps, factory) { var argsLen = arguments.length if (argsLen === 1) { factory = id id = undefined } else if (argsLen === 2) { factory = deps if (isArray(id)) { deps = id id = undefined } else { deps = undefined } } // Parse dependencies according to the module factory code if (!isArray(deps) && isFunction(factory)) { deps = typeof parseDependencies === "undefined" ? [] : parseDependencies(factory.toString()) } var meta = { id: id, uri: Module.resolve(id), deps: deps, factory: factory } // IE6-9 修正匿名模块的url if (!isWebWorker && !meta.uri && doc.attachEvent && typeof getCurrentScript !== "undefined") { var script = getCurrentScript() if (script) { meta.uri = script.src } } emit("define", meta)// nocache插件使用事件 // 模块的url为否值时,模块加载完成后使用Module.save(uri, anonymousMeta)更新模块的id、dependencies信息 meta.uri ? Module.save(meta.uri, meta) : anonymousMeta = meta } // define方法定义的模块,define函数执行时,存储该模块的id、dependcies等,该模块的状态为saved Module.save = function(uri, meta) { var mod = Module.get(uri) if (mod.status < STATUS.SAVED) { mod.id = meta.id || uri mod.dependencies = meta.deps || [] mod.factory = meta.factory mod.status = STATUS.SAVED emit("save", mod) } } // 获取cachedMods中通过url作为键存储的模块,或者创建新的模块 Module.get = function(uri, deps) { return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps)) } // 加载匿名模块 Module.use = function (ids, callback, uri) { var mod = Module.get(uri, isArray(ids) ? ids : [ids]) mod._entry.push(mod) mod.history = {} mod.remain = 1 mod.callback = function() { var exports = [] var uris = mod.resolve()// 依赖模块的url,数组形式 for (var i = 0, len = uris.length; i < len; i++) { exports[i] = cachedMods[uris[i]].exec() } // 执行匿名模块的回调函数,exports为其依赖 if (callback) { callback.apply(global, exports) } delete mod.callback// 执行完成后清除匿名模块的回调函数,怎样获取exports??? delete mod.history delete mod.remain delete mod._entry } mod.load() } // Public API // 加载匿名模块,由用户在载入js文件或script节点中主动调用,示例: // seajs.use(['jquery','./main'],function($,main){ // $(function(){ // main.init(); // }); // }); seajs.use = function(ids, callback) { Module.use(ids, callback, data.cwd + "_use_" + cid()) return seajs } Module.define.cmd = {} // 定义一个模块,示例 define(["jquey"],function(require,exports,mod){ }); global.define = Module.define // For Developers seajs.Module = Module data.fetchedList = fetchedList// 缓存加载成功的js文件模块信息 data.cid = cid // 模块已加载完成时,可seajs.require在另一个js脚本中快速调用,该脚本非必要遵循cmd规范 seajs.require = function(id) { var mod = Module.get(Module.resolve(id)) if (mod.status < STATUS.EXECUTING) { mod.onload() mod.exec() } return mod.exports }
8.config.js生成seajs基本配置
// 生成基本配置 data.base = loaderDir// 默认为当前文件夹路径 data.dir = loaderDir// 默认为当前文件夹路径 data.loader = loaderPath// 默认为当前文件路径 data.cwd = cwd// 当前页面的文件目录 data.charset = "utf-8" // @Retention(RetentionPolicy.SOURCE) // The CORS options, Don't set CORS on default. // //data.crossorigin = undefined // data.alias - 模块的简写名映射完整路径 // data.paths - 模块id内的路径映射 // data.vars - 模块id内的变量映射 // data.map - 映射配置 // data.debug - Debug模式 seajs.config = function(configData) { for (var key in configData) { var curr = configData[key] var prev = data[key] if (prev && isObject(prev)) { for (var k in curr) { prev[k] = curr[k] } } else { if (isArray(prev)) { curr = prev.concat(curr) // 确保data.base为绝对路径 }else if (key === "base") { if (curr.slice(-1) !== "/") { curr += "/" } curr = addBase(curr) } data[key] = curr } } emit("config", configData) return seajs }