https://github.com/seajs/seajs/issues/305
/** * @preserve SeaJS - A Module Loader for the Web * v1.2.0 | seajs.org | MIT Licensed */ /** * Base namespace for the framework. * seajs 为seajs 全局的命名空间 */ // 主要有两个作用 // 作用一:避免多次加载seajs而引起冲突 // 作用二:在异步加载seajs的时候,通过预先在页面端定义了seajs 和 config , use ,define 并对执行参数进行缓存来确保seajs可用性,避免报错。 this.seajs = { _seajs: this.seajs } //页面端异步加载seajs代码如下: /* ;(function(m, o, d, u, l, a, r) { if(m[d]) return; function f(n, t) { return function() { r.push(n, arguments); return t } } m[d] = a = { args: (r = []), config: f(0, a), use: f(1, a) }; m.define = f(2); u = o.createElement('script'); u.id = d + 'node'; u.src = '../../dist/sea.js'; l = o.getElementsByTagName('head')[0]; a = o.getElementsByTagName('base')[0]; a ? l.insertBefore(u, a) : l.appendChild(u); })(window, document, 'seajs'); */ /** * The version of the framework. It will be replaced with "major.minor.patch" * when building. * seajs 版本号 */ seajs.version = '1.2.0' /** * The private utilities. Internal use only. * 私有工具集 ,仅供内部调用使用。 */ seajs._util = {} /** * The private configuration data. Internal use only. * 私有配置数据,仅供内部调用使用。 */ seajs._config = { /** * Debug mode. It will be turned off automatically when compressing. * debug模式 。 在seajs被压缩后它会自动关闭debug模式。 */ debug: '%DEBUG%', /** * Modules that are needed to load before all other modules. * 需要预先被加载的模块 */ preload: [] } /** * The minimal language enhancement * JavaScript 语言增强 */ ;(function(util) { var toString = Object.prototype.toString var AP = Array.prototype //判断是否是个字符串 util.isString = function(val) { return toString.call(val) === '[object String]' } //判断是否是函数 util.isFunction = function(val) { return toString.call(val) === '[object Function]' } //判断是否是个正则 util.isRegExp = function(val) { return toString.call(val) === '[object RegExp]' } //判断是否是个对象 util.isObject = function(val) { return val === Object(val) } //判断是否是个数组 util.isArray = Array.isArray || function(val) { return toString.call(val) === '[object Array]' } //三元表达式为做兼容处理 //返回指定字符串在某个数组成员匹配中首次全文匹配的索引,如果没有匹配则返回 -1 util.indexOf = AP.indexOf ? function(arr, item) { return arr.indexOf(item) } : function(arr, item) { for (var i = 0; i < arr.length; i++) { if (arr[i] === item) { return i } } return -1 } //三元表达式为做兼容处理 //让数组成员全部执行一次一个指定的函数 , 对数组不做任何修改 var forEach = util.forEach = AP.forEach ? function(arr, fn) { arr.forEach(fn) } : function(arr, fn) { for (var i = 0; i < arr.length; i++) { fn(arr[i], i, arr) } } //三元表达式为做兼容处理 //让数据成员全部执行一次一个指定的函数,并返回一个新的数组,该数组为原数组成员执行回调后的结果 util.map = AP.map ? function(arr, fn) { return arr.map(fn) } : function(arr, fn) { var ret = [] forEach(arr, function(item, i, arr) { ret.push(fn(item, i, arr)) }) return ret } //三元表达式为做兼容处理 //让数据成员全部执行一次一个指定的函数,并返回一个新的数组,该数组为原数组成员执行回调后返回为true的成员 util.filter = AP.filter ? function(arr, fn) { return arr.filter(fn) } : function(arr, fn) { var ret = [] forEach(arr, function(item, i, arr) { if (fn(item, i, arr)) { ret.push(item) } }) return ret } //数组去重 util.unique = function(arr) { //声明空数组ret,空对象o var ret = [] var o = {} //将数组对象执行forEach方法,得到去重后的对象o , 巧妙 XD forEach(arr, function(item) { o[item] = 1 }) //对象以键值数组化 if (Object.keys) { ret = Object.keys(o) } else { for (var p in o) { if (o.hasOwnProperty(p)) { ret.push(p) } } } //返回数组 return ret } //以对象键值数组化 util.keys = Object.keys if (!util.keys) { util.keys = function(o) { var ret = [] for (var p in o) { if (o.hasOwnProperty(p)) { ret.push(p) } } return ret } } //当前时刻 util.now = Date.now || function() { return new Date().getTime() } })(seajs._util) /** * The tiny console support * 提供 tiny console */ ;(function(util, config) { var AP = Array.prototype /** * The safe wrapper of console.log/error/... */ util.log = function() { if (typeof console !== 'undefined') { //借助Array原生方法把arguments转换为JS数组 var args = AP.slice.call(arguments) var type = 'log' var last = args[args.length - 1] // pop方法为删除数组最后一个元素并返回删除内容 console[last] && (type = args.pop()) // Only show log info in debug mode // 只在debug模式下出现log信息 if (type === 'log' && !config.debug) return var out = type === 'dir' ? args[0] : AP.join.call(args, ' ') console[type](out) } } })(seajs._util, seajs._config) /** * Path utilities for the framework * 框架路径工具集 */ ;(function(util, config, global) { var DIRNAME_RE = /.*(?=\/.*$)/ //↑用于提取文件路径的目录部分 var MULTIPLE_SLASH_RE = /([^:\/])\/\/+/g //↑用于削减多个‘/’情况 var FILE_EXT_RE = /\.(?:css|js)$/ //↑用于判断是否以 .css 或者 .js 结尾 var ROOT_RE = /^(.*?\w)(?:\/|$)/ //↑这个用于提取路径的目录部分 /** * Extracts the directory portion of a path. * dirname('a/b/c.js') ==> 'a/b/' * dirname('d.js') ==> './' * @see http://jsperf.com/regex-vs-split/2 * 用于提取文件路径的目录部分 */ function dirname(path) { var s = path.match(DIRNAME_RE) return (s ? s[0] : '.') + '/' } /** * Canonicalizes a path. * realpath('./a//b/../c') ==> 'a/c' * 规范化路径 */ function realpath(path) { MULTIPLE_SLASH_RE.lastIndex = 0 // 'file:///a//b/c' ==> 'file:///a/b/c' // 'http://a//b/c' ==> 'http://a/b/c' if (MULTIPLE_SLASH_RE.test(path)) { path = path.replace(MULTIPLE_SLASH_RE, '$1\/') //匹配到多个/情况后用一个/予以替换 } // 'a/b/c', just return. //如果路径中没有出现 '.' 则返回路径 if (path.indexOf('.') === -1) { return path } var original = path.split('/') var ret = [], part //用于处理路径中存在a/b/../c 的形式,该形式下将会直接被替换成为 a/c for (var i = 0; i < original.length; i++) { part = original[i] if (part === '..') { if (ret.length === 0) { throw new Error('The path is invalid: ' + path) } ret.pop() } else if (part !== '.') { ret.push(part) } } return ret.join('/') } /** * Normalizes an uri. * 正常化uri 原因在于在seajs内部require模块时候对于后缀名都是缺省的,正常化的功能就在于此 */ function normalize(uri) { // 规范化路径,调用realpath方法 uri = realpath(uri) // 获取路径倒数第一个字符 var lastChar = uri.charAt(uri.length - 1) // 如果是‘/’则返回uri if (lastChar === '/') { return uri } // Adds the default '.js' extension except that the uri ends with #. // ref: http://jsperf.com/get-the-last-character // 如果最后一个字符是‘#’ , 去‘#’ if (lastChar === '#') { uri = uri.slice(0, -1) } // 如果uri中同时不存在‘?’和 路径中不以.css和.js结尾的,统一给路径加上.js else if (uri.indexOf('?') === -1 && !FILE_EXT_RE.test(uri)) { uri += '.js' } // Remove ':80/' for bug in IE // IE bug :80/ -> / if (uri.indexOf(':80/') > 0) { uri = uri.replace(':80/', '/') } //返回正常化路径 return uri } /** * Parses alias in the module id. Only parse the first part. * 解析模块id中的别名。 */ function parseAlias(id) { // #xxx means xxx is already alias-parsed. // #xxx 意味着 xxx 别名已经解析完毕。 //判断首个字符串字符是否为# 如果含#,则舍去# 并返回字符串 if (id.charAt(0) === '#') { return id.substring(1) } // 获取用户配置的别名配置信息 var alias = config.alias // Only top-level id needs to parse alias. // 顶级标识不以点(.)或斜线(/)或含有(://), 会相对模块系统的基础路径(即 SeaJS 的 base 路径)来解析 if (alias && isTopLevel(id)) { //把id用(/)进行分割 var parts = id.split('/') // 把首个数组的值做为关键字 var first = parts[0] //如果alilas内含有first的属性 if (alias.hasOwnProperty(first)) { //则把alias内first的属性值给parts[0],实则替换路径,替换成顶级标识 parts[0] = alias[first] //重新拼接字符串 id = parts.join('/') } } //返回 id return id } // 新建mapCache对象 var mapCache = {} /** * Converts the uri according to the map rules. * 根据map中的规则转换uri * 可以参考 https://github.com/seajs/seajs/issues/270 非常详细! */ function parseMap(uri) { // map: [[match, replace], ...] // map来自于用户的自定义配置项seajs.config var map = config.map || [] // 如果map为空 则直接返回 uri if (!map.length) return uri //不为空的情况下,原始uri将赋值给 ret var ret = uri // Apply all matched rules in sequence. // 匹配序列中所有的规则 for (var i = 0; i < map.length; i++) { var rule = map[i] if (util.isArray(rule) && rule.length === 2) { var m = rule[0] if (util.isString(m) && ret.indexOf(m) > -1 || util.isRegExp(m) && m.test(ret)) { //对匹配到的地方予以替换 ret = ret.replace(m, rule[1]) } } else if (util.isFunction(rule)) { ret = rule(ret) } } if (ret !== uri) { //缓存原有uri mapCache[ret] = uri } //返回经过匹配的新的uri return ret } /** * Gets the original uri. * 获取原始uri */ function unParseMap(uri) { return mapCache[uri] || uri } /** * Converts id to uri. * 把 id 转化为 uri */ function id2Uri(id, refUri) { //id不存在则返回空 if (!id) return '' //解析别名 id = parseAlias(id) //如果refUri没有传入的时候 refUri采用pageUri refUri || (refUri = pageUri) var ret // 如果 传入的 id 本身就是一个绝对地址 // absolute id if (isAbsolute(id)) { ret = id } // relative id else if (isRelative(id)) { // Converts './a' to 'a', to avoid unnecessary loop in realpath. if (id.indexOf('./') === 0) { id = id.substring(2) } ret = dirname(refUri) + id } // root id else if (isRoot(id)) { ret = refUri.match(ROOT_RE)[1] + id } // top-level id // 顶级标识 附加 base ,base为seajs路径 else { ret = config.base + '/' + id } // 返回正常化的uri return normalize(ret) } //是否为绝对路径 function isAbsolute(id) { return id.indexOf('://') > 0 || id.indexOf('//') === 0 } //是否为相对路径 function isRelative(id) { return id.indexOf('./') === 0 || id.indexOf('../') === 0 } //是否为根路径 function isRoot(id) { return id.charAt(0) === '/' && id.charAt(1) !== '/' } //判断id 是否为顶级标识 关于标识具体可以查看 https://github.com/seajs/seajs/issues/258 //顶级标识不以点(.)或斜线(/)或含有(://), 会相对模块系统的基础路径(即 SeaJS 的 base 路径)来解析 function isTopLevel(id) { var c = id.charAt(0) return id.indexOf('://') === -1 && c !== '.' && c !== '/' } /** * Normalizes pathname to start with '/' * Ref: https://groups.google.com/forum/#!topic/seajs/9R29Inqk1UU * 所有路径(除了根路径)前添加 ‘/’ */ function normalizePathname(pathname) { if (pathname.charAt(0) !== '/') { pathname = '/' + pathname } return pathname } var loc = global['location'] // 举例 : // uri : http://www.google.com/pigcan/sohandsome.html // loc.protocol -> http: // loc.host -> www.google.com // normalizePathname(loc.pathname) -> /pigcan/sohandsome.html var pageUri = loc.protocol + '//' + loc.host + normalizePathname(loc.pathname) // local file in IE: C:\path\to\xx.js if (pageUri.indexOf('\\') > 0) { pageUri = pageUri.replace(/\\/g, '/') } util.dirname = dirname // 该方法用以提取路径的目录部分 //dirname('a/b/c.js') ==> 'a/b/' dirname('d.js') ==> './' util.realpath = realpath // 该方法将规范化路径,从而获得真实路径 //realpath('./a//b/../c') ==> 'a/c' 'file:///a//b/c' ==> 'file:///a/b/c' 'http://a//b/c' ==> 'http://a/b/c' util.normalize = normalize // 该方法会加上缺省的后缀名 .js util.parseAlias = parseAlias //解析别名 util.parseMap = parseMap //根据map中的规则转换uri util.unParseMap = unParseMap //替换转换后的uri为原始uri util.id2Uri = id2Uri //把id转换为uri util.isAbsolute = isAbsolute //是否为绝对路径 util.isTopLevel = isTopLevel //是否是顶级标识 util.pageUri = pageUri //当前页uri })(seajs._util, seajs._config, this) /** * Utilities for fetching js and css files. * 获取js和css文件的工具集 */ ;(function(util, config) { var doc = document var head = doc.head || doc.getElementsByTagName('head')[0] || doc.documentElement var baseElement = head.getElementsByTagName('base')[0] var IS_CSS_RE = /\.css(?:\?|$)/i var READY_STATE_RE = /loaded|complete|undefined/ var currentlyAddingScript var interactiveScript //加载资源文件文件 util.fetch = function(url, callback, charset) { //获取的文件是不是css var isCSS = IS_CSS_RE.test(url) //如果是css创建节点 link 否则 则创建script节点 var node = document.createElement(isCSS ? 'link' : 'script') //如果存在charset 如果charset不是function类型,那就直接对节点设置charset ,如果是function如下例: /* seajs.config({ charset: function(url) { // xxx 目录下的文件用 gbk 编码加载 if (url.indexOf('http://example.com/js/xxx') === 0) { return 'gbk'; } // 其他文件用 utf-8 编码 return 'utf-8'; } }); */ if (charset) { var cs = util.isFunction(charset) ? charset(url) : charset cs && (node.charset = cs) } //assets执行完毕后执行callback ,如果自定义callback为空,则赋予noop 为空函数 assetOnload(node, callback || noop) //如果是样式 …… 如果是 脚本 …… async 详见:https://github.com/seajs/seajs/issues/287 if (isCSS) { node.rel = 'stylesheet' node.href = url } else { node.async = 'async' node.src = url } // For some cache cases in IE 6-9, the script executes IMMEDIATELY after // the end of the insertBefore execution, so use `currentlyAddingScript` // to hold current node, for deriving url in `define`. // 之下这些代码都是为了兼容ie // 假如A页面在含有base标签,此时A页面有个按钮具有请求B页面的功能,并且请求过来的内容将插入到A页面的某个div中 // B页面有一些div,并且包含一个可执行的script // 其他浏览器都会在异步请求完毕插入页面后执行该script 但是 ie 不行,必须要插入到base标签前。 currentlyAddingScript = node // ref: #185 & http://dev.jquery.com/ticket/2709 // 关于base 标签 http://www.w3schools.com/tags/tag_base.asp baseElement ? head.insertBefore(node, baseElement) : head.appendChild(node) currentlyAddingScript = null } //资源文件加载完毕后执行回调callback function assetOnload(node, callback) { if (node.nodeName === 'SCRIPT') { scriptOnload(node, callback) } else { styleOnload(node, callback) } } //资源文件加载完执行回调不是所有浏览器都支持一种形式,存在兼容性问题 //http://www.fantxi.com/blog/archives/load-css-js-callback/ 这篇文章非常不错 //加载脚本完毕后执行回调 function scriptOnload(node, callback) { // onload为IE6-9/OP下创建CSS的时候,或IE9/OP/FF/Webkit下创建JS的时候 // onreadystatechange为IE6-9/OP下创建CSS或JS的时候 node.onload = node.onerror = node.onreadystatechange = function() { //正则匹配node的状态 //readyState == "loaded" 为IE/OP下创建JS的时候 //readyState == "complete" 为IE下创建CSS的时候 -》在js中做这个正则判断略显多余 //readyState == "undefined" 为除此之外浏览器 if (READY_STATE_RE.test(node.readyState)) { // Ensure only run once and handle memory leak in IE // 配合 node = undefined 使用 主要用来确保其只被执行一次 并 处理了IE 可能会导致的内存泄露 node.onload = node.onerror = node.onreadystatechange = null // Remove the script to reduce memory leak // 在存在父节点并出于非debug模式下移除node节点 if (node.parentNode && !config.debug) { head.removeChild(node) } // Dereference the node // 废弃节点,这个做法其实有点巧妙,对于某些浏览器可能同时支持onload或者onreadystatechange的情况,只要支持其中一种并执行完一次之后,把node释放,巧妙实现了可能会触发多次回调的情况 node = undefined //执行回调 callback() } } } //加载样式完毕后执行回调 function styleOnload(node, callback) { // for Old WebKit and Old Firefox // iOS 5.1.1 还属于old --! 但是 iOS6中 536.13 // 这里用户采用了代理可能会造成一点的勿扰,可能代理中他是一个oldwebkit浏览器 但是实质却不是 if (isOldWebKit || isOldFirefox) { util.log('Start poll to fetch css') setTimeout(function() { poll(node, callback) }, 1) // Begin after node insertion // 延迟执行 poll 方法,确保node节点已被插入 } else { node.onload = node.onerror = function() { node.onload = node.onerror = null node = undefined callback() } } } function poll(node, callback) { var isLoaded // for WebKit < 536 // 如果webkit内核版本低于536则通过判断node节点时候含属性sheet if (isOldWebKit) { if (node['sheet']) { isLoaded = true } } // for Firefox < 9.0 else if (node['sheet']) { try { //如果存在cssRules属性 if (node['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' // 在Firefox13.0开始把'NS_ERROR_DOM_SECURITY_ERR'改成了'SecurityError' // 但是这边处理是小于等于firefox9.0的所以在异常处理上还是依赖与'NS_ERROR_DOM_SECURITY_ERR' if (ex.name === 'NS_ERROR_DOM_SECURITY_ERR') { isLoaded = true } } } setTimeout(function() { if (isLoaded) { // Place callback in here due to giving time for style rendering. callback() } else { poll(node, callback) } }, 1) } function noop() { } //用来获取当前插入script util.getCurrentScript = function() { // 如果已经获取到当前插入的script节点,则直接返回 if (currentlyAddingScript) { return currentlyAddingScript } // For IE6-9 browsers, the script onload event may not fire right // after the 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 // 在IE6-9浏览器中,script的onload事件有时候并不能在script加载后触发 // 需要遍历script的节点才能知道,当前脚本状态为 'interactive' if (interactiveScript && interactiveScript.readyState === 'interactive') { return interactiveScript } var scripts = head.getElementsByTagName('script') for (var i = 0; i < scripts.length; i++) { var script = scripts[i] if (script.readyState === 'interactive') { interactiveScript = script return script } } } //获取script的绝对路径 util.getScriptAbsoluteSrc = function(node) { return node.hasAttribute ? // non-IE6/7 node.src : // see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx node.getAttribute('src', 4) } // 创建样式节点 util.importStyle = function(cssText, id) { // Don't add multi times //一个id不要添加多次 如果页面中已经存在该id则直接返回 if (id && doc.getElementById(id)) return //创建style标签,并指定标签id,并插入head,ie和其他标准浏览器插入css样式存在兼容性问题,具体如下: var element = doc.createElement('style') id && (element.id = id) // Adds to DOM first to avoid the css hack invalid head.appendChild(element) // IE if (element.styleSheet) { element.styleSheet.cssText = cssText } // W3C else { element.appendChild(doc.createTextNode(cssText)) } } //获取 UA 信息 var UA = navigator.userAgent // `onload` event is supported in WebKit since 535.23 // Ref: // - https://bugs.webkit.org/show_activity.cgi?id=38995 // css onload 事件的支持 从webkit 内核版本 535.23 开始 var isOldWebKit = Number(UA.replace(/.*AppleWebKit\/(\d+)\..*/, '$1')) < 536 // `onload/onerror` event is supported since Firefox 9.0 // onload/onerror 这个事件是从firefox9.0开始支持的,在判断中首先判断UA是否是Firefox 并且 在存在onload // Ref: // - https://bugzilla.mozilla.org/show_bug.cgi?id=185236 // - https://developer.mozilla.org/en/HTML/Element/link#Stylesheet_load_events var isOldFirefox = UA.indexOf('Firefox') > 0 && !('onload' in document.createElement('link')) /** * References: * - http://unixpapa.com/js/dyna.html * - ../test/research/load-js-css/test.html * - ../test/issues/load-css/test.html * - http://www.blaze.io/technical/ies-premature-execution-problem/ */ })(seajs._util, seajs._config, this) /** * The parser for dependencies * 解析模块依赖 */ ;(function(util) { var REQUIRE_RE = /(?:^|[^.$])\brequire\s*\(\s*(["'])([^"'\s\)]+)\1\s*\)/g util.parseDependencies = function(code) { // Parse these `requires`: // var a = require('a'); // someMethod(require('b')); // require('c'); // ... // Doesn't parse: // someInstance.require(...); // 解析含有require字段的依赖 , 但是不解析实例化之后的实例的依赖 var ret = [], match code = removeComments(code) //从头开始匹配 lastIndex的值会随着匹配过程的进行而修改 REQUIRE_RE.lastIndex = 0 // var asd = require('a'); 经过正则匹配后结果为 ["require('asd')","'" ,"asd"] while ((match = REQUIRE_RE.exec(code))) { if (match[2]) { ret.push(match[2]) } } //返回数组去重值 return util.unique(ret) } //删除注释 , 其实就是避免在正则匹配的时候,注释内还含有require信息,导致加载不必要的模块 // See: research/remove-comments-safely function removeComments(code) { return code .replace(/^\s*\/\*[\s\S]*?\*\/\s*$/mg, '') // block comments .replace(/^\s*\/\/.*$/mg, '') // line comments } })(seajs._util) /** * The Module constructor and its methods */ ;(function(seajs, util, config) { //详细解释具体查看 https://github.com/seajs/seajs/issues/303 //不过接下来也会大概举例出来。 //假设模块a.js并且其依赖于模块b.js 在页面中使用seajs.use('./a') //调用use方法后,seajs会在浏览器端创建<script src='a.js'></script> 随后通过一系列的方法得到a.js的绝对路径,并开始下载a.js //此时模块的状态为FETCHING //当a.js的onload时间触发时,文件已经下载到浏览器端,此时模块状态为FETCHED //在onload事件触发前,a.js中的define代码已经执行,但是由于是匿名模块,该模块的uri信息需要得到onload事件触发后才能拿到 //获取到后,会将模块 a 的信息存储到内部变量 cachedModules 里,此时模块状态变成 STATUS.SAVED //存储模块的信息包括: //1.factory - 就是 define 接收的函数 function(require, exports) { ... } //2.uri - 在 onload 触发后,通过 url 拿到 //3.dependencies - 通过 factory.toString 方法拿到源码,然后正则匹配 require 拿到 //4.status - 就是这里讨论的模块状态 //可见当模块状态处于 SAVED 时,模块的依赖信息已经获取到。 //拿到 dependencies,接下来就很自然了:开始下载这些依赖模块,重复上面的步骤,直到所有依赖模块的状态都变成 SAVED。 //当依赖的所有模块都保存完毕时,模块 a 的状态就变成了 STATUS.READY var cachedModules = {} var cachedModifiers = {} var compileStack = [] var STATUS = { 'FETCHING': 1, // The module file is fetching now. 模块正在获取中 'FETCHED': 2, // The module file has been fetched. 模块获取完毕 'SAVED': 3, // The module info has been saved. 模块信息已被存储 'READY': 4, // All dependencies and self are ready to compile. 所有依赖以及自身等待编译 'COMPILING': 5, // The module is in compiling now. 模块编译中 'COMPILED': 6 // The module is compiled and module.exports is available. 模块编译完毕,module.exports可用 } //另外 源码中大量采用了 a && b && c && d 或者 a || b || c || d // &&中返回的是第一个返回为false的值 而 || 返回的则是第一个返回true的值 function Module(uri, status) { this.uri = uri //如果status缺省,则赋值为0 this.status = status || 0 // this.id is set when saving // this.dependencies is set when saving // this.factory is set when saving // this.exports is set when compiling // this.parent is set when compiling // this.require is set when compiling } Module.prototype._use = function(ids, callback) { //查看传入的ids为字符串还是数组 //如果传入的ids为字符串 例:ids = './a' -> ids =['./a'] //如果传入的ids为数组 例: ids = ['./a','./b'] -> ids = ['./a','./b'] (原样不变) util.isString(ids) && (ids = [ids]) //得到uri 或者 uri数组 var uris = resolve(ids, this.uri) this._load(uris, function() { //util.map : 让数据成员全部执行一次一个指定的函数,并返回一个新的数组,该数组为原数组成员执行回调后的结果 var args = util.map(uris, function(uri) { return uri ? cachedModules[uri]._compile() : null }) if (callback) { callback.apply(null, args) } }) } // _load()方法主要会先判断那些资源文件还没有ready,如果全部资源文件都处于ready状态就执行callback() // 在这其中还会做循环依赖的判断,以及对没有加载的js执行加载 Module.prototype._load = function(uris, callback) { //util.filter : 让数据成员全部执行一次一个指定的函数,并返回一个新的数组,该数组为原数组成员执行回调后返回为true的成员 //unLoadedUris是那些没有被编译的模块uri数组 var unLoadedUris = util.filter(uris, function(uri) { //返回执行函数后布尔值为true的成员,在uri存在并且在内部变量cacheModules中不存在或者或者它在存储信息中status的值小于STATUS.READY时返回true // STATUS.READY值为4,小于四则可能的情况是获取中,下载中或者还有依赖模块未模块信息saved return uri && (!cachedModules[uri] || cachedModules[uri].status < STATUS.READY) }) //如果模块所依赖的模块全部被加载执行了,执行回调并退出函数体 var length = unLoadedUris.length if (length === 0) { callback() return } //还未加载的模块个数 var remain = length //创建闭包,尝试去加载那些没有加载的模块 for (var i = 0; i < length; i++) { (function(uri) { //判断如果在内部变量cachedModules里面并不存在该uri的存储信息则实例化一个Module对象 var module = cachedModules[uri] || (cachedModules[uri] = new Module(uri, STATUS.FETCHING)) //如果模块的状态值大于等于2,也就意味着模块已经被下载好并已经存在于本地了 //这个时候执行onFetched() //否则则调用fetch(uri, onFetched) ,尝试下载资源文件,onload后执行回调onFetched方法 module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched) function onFetched() { // cachedModules[uri] is changed in un-correspondence case module = cachedModules[uri] //但模块的状态值为大于等于STATUS.SAVED的时候,也就意味着该模块所有的依赖信息已经被拿到 if (module.status >= STATUS.SAVED) { //getPureDependencies:得到不存在循环依赖的依赖数组 var deps = getPureDependencies(module) //如果依赖数组不为空 if (deps.length) { //再次执行_load()方法,直到全部依赖加载完成后执行回调 Module.prototype._load(deps, function() { cb(module) }) } //如果依赖数组为空的情况下,直接执行cb(module) else { cb(module) } } // Maybe failed to fetch successfully, such as 404 or non-module. // In these cases, module.status stay at FETCHING or FETCHED. // 如果获取失败后,比如404或者不符合模块化规范 //在这种情形下,module.status会维持在 FETCHING 或者 FETCHED else { cb() } } })(unLoadedUris[i]) } // cb 方法 - 加载完所有模块执行回调 function cb(module) { // 如果module的存储信息存在,那么修改它的module存储信息中的status的值,修改为 STATUS.READY module && (module.status = STATUS.READY) // 只有当所有模块加载完毕后执行回调。 --remain === 0 && callback() } } // 执行factory 返回module.exports , 如果该module还对其余module存在依赖也会让其执行返回exports Module.prototype._compile = function() { var module = this // 如果执行编译时module.staus 显示为 已经编译好了 // 则直接返回该module.exports if (module.status === STATUS.COMPILED) { return module.exports } // Just return null when: // 1. the module file is 404. // 2. the module file is not written with valid module format. // 3. other error cases. // 如果到编译了,module的状态还未ready则返回null // 一般这种情形是module不存在或者module不符合相关规范或者其他一些错误 - 严重超时 if (module.status < STATUS.READY) { return null } // 设置module的状态值为 STATUS.COMPILING // 假设现有模块a其status的值为READY,其的含义是 ready to compile。此时模块 a 的 factory 函数还没有执行。 // 当需要执行时,才会执行。模块 a 的 factory 函数正在执行时,模块 a 的状态为 COMPILING。 module.status = STATUS.COMPILING // function require(id) { // resolve函数主要用于把ids -》 uris var uri = resolve(id, module.uri) // cachedModules为存储模块信息的内部变量,这里将其赋值给child var child = cachedModules[uri] // Just return null when uri is invalid. // 如果不存在该模块存储信息,则返回null if (!child) { return null } // Avoids circular calls. // 如果child的状态值为STATUS.COMPILING ,为了避免回调中的循环依赖则直接返回child.exports if (child.status === STATUS.COMPILING) { return child.exports } // 指向初始化时调用当前模块的模块。根据该属性,可以得到模块初始化时的 Call Stack. child.parent = module //返回 child的module.exports return child._compile() } // async 方法可用来异步加载模块,并在加载完成后执行指定回调。 require.async = function(ids, callback) { module._use(ids, callback) } // 使用模块系统内部的路径解析机制来解析并返回模块路径。该函数不会加载模块,只返回解析后的绝对路径。 require.resolve = function(id) { return resolve(id, module.uri) } // 通过该属性,可以查看到模块系统加载过的所有模块。 // 在某些情况下,如果需要重新加载某个模块,可以得到该模块的 uri, 然后通过 delete require.cache[uri] 来将其信息删除掉。这样下次使用时,就会重新获取。 require.cache = cachedModules // require 是一个方法,用来获取其他模块提供的接口。 module.require = require // exports 是一个对象,用来向外提供模块接口。 module.exports = {} // 指向 define(factory) 中 factory 参数。 var factory = module.factory // 判断factory是否为函数 if (util.isFunction(factory)) { // 假设模块 a 的 factory 执行时,假设a内部含有b的依赖,因此也会触发模块 b 的执行,模块 b 有可能还有依赖模块,比如 c,这时会继续触发模块 c 的执行,这就形成一个 stack: // 这个信息,就存储在内部变量 compileStack 里。 /* 模块 a 开始执行 模块 b 开始执行 模块 c 开始执行 模块 c 执行完毕 模块 b 执行完毕 模块 a 执行完毕 */ compileStack.push(module) // 得到module.exports runInModuleContext(factory, module) compileStack.pop() } // factory不是函数但是它又存在的 这个时候module.exports直接赋值factory // 换言之 factory 为对象、字符串等非函数类型时,表示模块的接口就是该对象、字符串等值。 // define({ "foo": "bar" }); 或者 define('I am a template. My name is {{name}}.'); else if (factory !== undefined) { module.exports = factory } //把module.status的状态值设置为 STATUS.COMPILED , 意义为编译完毕 module.status = STATUS.COMPILED // 执行modify 功能 ,如果程序中有设置modify的相关内容则取代原有的module.exports execModifiers(module) //最终返回module.exports 当前模块对外提供的 return module.exports } //define 定义 ,id : 模块id , deps : 模块依赖 , factory Module._define = function(id, deps, factory) { var argsLength = arguments.length // define(factory) // 如果参数只有一位,那默认为factory,其余配对信息将由打包工具完成,需要走注意的一点是factory不可缺省! if (argsLength === 1) { factory = id id = undefined } // define(id || deps, factory) // 如果参数有两位 else if (argsLength === 2) { //赋值factory为deps -》 因为参数缺省导致这么书写 factory = deps // deps 为 undefined deps = undefined //判断id时候为一个数组, // define(deps, factory) if (util.isArray(id)) { deps = id id = undefined } } // Parses dependencies. //解析依赖关系 // 如果deps不是数组类型,同时factory是函数 if (!util.isArray(deps) && util.isFunction(factory)) { // 函数体内正则匹配require字符串,并形成数组返回赋值给deps deps = util.parseDependencies(factory.toString()) } //设置元信息 var meta = { id: id, dependencies: deps, factory: factory } var derivedUri // Try to derive uri in IE6-9 for anonymous modules. if (document.attachEvent) { // Try to get the current script. // 得到当前script的节点 var script = util.getCurrentScript() // 如果script节点存在 if (script) { // 得到原始uri地址 derivedUri = util.unParseMap(util.getScriptAbsoluteSrc(script)) } if (!derivedUri) { util.log('Failed to derive URI from interactive script for:', factory.toString(), 'warn') // NOTE: If the id-deriving methods above is failed, then falls back // to use onload event to get the uri. } } // Gets uri directly for specific module. var resolvedUri = id ? resolve(id) : derivedUri if (resolvedUri) { // If the first module in a package is not the cachedModules[derivedUri] // self, it should assign it to the correct module when found. if (resolvedUri === derivedUri) { var refModule = cachedModules[derivedUri] if (refModule && refModule.packageUri && refModule.status === STATUS.SAVED) { cachedModules[derivedUri] = null } } var module = save(resolvedUri, meta) // Handles un-correspondence case: if (derivedUri) { // cachedModules[derivedUri] may be undefined in combo case. if ((cachedModules[derivedUri] || {}).status === STATUS.FETCHING) { cachedModules[derivedUri] = module module.packageUri = derivedUri } } else { firstModuleInPackage || (firstModuleInPackage = module) } } else { // Saves information for "memoizing" work in the onload event. anonymousModuleMeta = meta } } // 逐次获取编译后module的module.exportsse Module._getCompilingModule = function() { return compileStack[compileStack.length - 1] } // 通过 seajs.find,可以快速查到到 seajs.cache 中的特定模块。 // selector 支持字符串 和 正则表达式 // 最终把模块的exports暴露给外界 Module._find = function(selector) { var matches = [] util.forEach(util.keys(cachedModules), function(uri) { if (util.isString(selector) && uri.indexOf(selector) > -1 || util.isRegExp(selector) && selector.test(uri)) { var module = cachedModules[uri] module.exports && matches.push(module.exports) } }) var length = matches.length if (length === 1) { matches = matches[0] } else if (length === 0) { matches = null } return matches } //在拿到 module.exports 对象、正式返回给模块系统前,我们可以做点手脚:对 module.exports 进行加工,这就是 seajs.modify 功能。 // 关于modify相关内容具体可以查看: // https://github.com/seajs/seajs/issues/274 Module._modify = function(id, modifier) { //通过id获取绝对路径 var uri = resolve(id) //在cachedModules中寻找所需要的uri所对应的存储信息,该信息包含1.factory2.uri3.dependencies4.status var module = cachedModules[uri] // 如果module存储信息存在(代表已经被saved)并且module的状态为已经编译完毕 if (module && module.status === STATUS.COMPILED) { //执行factory 返回module.exports runInModuleContext(modifier, module) } else { // cachedModifiers[uri] 查看是否存在,如果不存在赋值新数组,并添加数组成员 cachedModifiers[uri] || (cachedModifiers[uri] = []) cachedModifiers[uri].push(modifier) } //返回seajs return seajs } // For plugin developers // 用于插件开发 // Module.STATUS 为模块的状态值 Module.STATUS = STATUS // Module._resolve id -> uri Module._resolve = util.id2Uri // Module._fetch 资源文件加载 Module._fetch = util.fetch // Module.cache 模块系统加载过的所有模块 Module.cache = cachedModules // Helpers // ------- var fetchingList = {} var fetchedList = {} var callbackList = {} var anonymousModuleMeta = null var firstModuleInPackage = null var circularCheckStack = [] //firstModuleInPackage 这个,和 un-correspondence 的情况,可以看 test/issues/un-correspondence //有两个作用: //1. 一是当 id 解析路径与真实路径不匹配时,让这两个路径都指向同一个模块。 //2. 二是当一个文件里,有多个模块时,使得访问路径指向文件里的第一个模块。 //resolve函数主要用于把ids -》 uris function resolve(ids, refUri) { if (util.isString(ids)) { // 是字符串的话执行Module._resolve -> util.id2Uri // 在id2Uri这个函数中主要是把id转换为Uri(此uri被normalize过) return Module._resolve(ids, refUri) } // util.map 让数据成员全部执行一次一个指定的函数,并返回一个新的数组,该数组为原数组成员执行回调后的结果 // 如果ids为数组 ,数组内成员执行resolve函数,并返回执行结果的新数组 return util.map(ids, function(id) { return resolve(id, refUri) }) } function fetch(uri, callback) { // 根据map中的规则替换uri为新的请求地址 var requestUri = util.parseMap(uri) // 首先在已获取列表中查找是否含有requestUri记录 if (fetchedList[requestUri]) { // See test/issues/debug-using-map // 这个时候将原始uri的module存储信息 刷新到 通过map重定义的requestUri上 cachedModules[uri] = cachedModules[requestUri] // 执行callback 并返回 callback() return } //在获取列表中查询 requestUri 的存储信息 if (fetchingList[requestUri]) { //在callbacklist中加入该uri对应下的callback 并返回 callbackList[requestUri].push(callback) return } // 如果尝试获取的模块都未出现在fetchedList和fetchingList中,则分别在请求列表和回调列表中添加其信息 fetchingList[requestUri] = true callbackList[requestUri] = [callback] // Fetches it Module._fetch( requestUri, function() { fetchedList[requestUri] = true // Updates module status // 如果 module.status 等于 STATUS.FECTCHING ,则修改module状态为FETCHED var module = cachedModules[uri] if (module.status === STATUS.FETCHING) { module.status = STATUS.FETCHED } // Saves anonymous module meta data // 如果存在匿名模块元信息 if (anonymousModuleMeta) { save(uri, anonymousModuleMeta) anonymousModuleMeta = null } // Assigns the first module in package to cachedModules[uri] // See: test/issues/un-correspondence if (firstModuleInPackage && module.status === STATUS.FETCHED) { cachedModules[uri] = firstModuleInPackage firstModuleInPackage.packageUri = uri } firstModuleInPackage = null // Clears 清除获取信息 if (fetchingList[requestUri]) { delete fetchingList[requestUri] } // Calls callbackList 统一执行回调 if (callbackList[requestUri]) { util.forEach(callbackList[requestUri], function(fn) { fn() }) delete callbackList[requestUri] } }, config.charset ) } // function save(uri, meta) { //尝试获取uri的存储信息,如果存在则直接从内部变量cachedModules中获取,否则新建一个Module对象 var module = cachedModules[uri] || (cachedModules[uri] = new Module(uri)) // Don't override already saved module // if (module.status < STATUS.SAVED) { // Lets anonymous module id equal to its uri // 让那些匿名模块的id 等于 meta.id 如果meta.id不存在 则为 uri module.id = meta.id || uri module.dependencies = resolve( // 返回那些存在依赖关系的module的dependencies的绝对地址 util.filter(meta.dependencies || [], function(dep) { return !!dep }), uri) module.factory = meta.factory // Updates module status // 更新模块状态 为saved // 假设存在模块a ,在 onload 事件触发前,a.js 中的 define 代码已执行。 // 但由于是匿名模块,该模块的 uri 信息,需要等到 onload 触发后才能获取到。 // 获取到后,会将模块 a 的信息存储到内部变量 cachedModules 里,此时模块状态变成 STATUS.SAVED module.status = STATUS.SAVED } // 返回module return module } // 运行 fn -> factory , 得到module.exports function runInModuleContext(fn, module) { var ret = fn(module.require, module.exports, module) // 在ret不为undefined情况下 ,赋值该module的exports值为fn的执行结果 if (ret !== undefined) { module.exports = ret } } // 在拿到 module.exports 对象、正式返回给模块系统前,我们可以做点手脚:对 module.exports 进行加工,这就是 seajs.modify 功能。 function execModifiers(module) { // 得到模块uri var uri = module.uri // 内部变量 cachedModifiers 就是用来存储用户通过 seajs.modify 方法定义的修改点 // 查看该uri是否又被modify更改过 var modifiers = cachedModifiers[uri] // 如果存在修改点 if (modifiers) { // 对修改点统一执行factory,返回修改后的module.exports util.forEach(modifiers, function(modifier) { runInModuleContext(modifier, module) }) // 删除 modify 方法定义的修改点 ,避免再次执行时再次执行 delete cachedModifiers[uri] } } //获取纯粹的依赖关系 , 得到不存在循环依赖关系的依赖数组 function getPureDependencies(module) { var uri = module.uri return util.filter(module.dependencies, function(dep) { //是先推入被检查模块的uri到循环依赖检查栈中 circularCheckStack = [uri] //接下来检查模块uri是否和其依赖的模块存在循环依赖 //传入的两个值分别为cachedModules[dep]:依赖模块的存储信息 , 当前被检查的模块的uri //ps:cachedModules[dep] 这个内部变量的存在时,代表其模块状态已经为saved var isCircular = isCircularWaiting(cachedModules[dep], uri) //如果存在循环依赖则在检查栈中推入uri , 并打印循环依赖信息 if (isCircular) { circularCheckStack.push(uri) printCircularLog(circularCheckStack) } // 如果存在循环依赖返回false,如果不存在则返回true return !isCircular }) } //检查模块间是否存在循环等待,返回布尔值 function isCircularWaiting(module, uri) { // 如果主模块所依赖模块存储信息不存在 或者 模块的状态值等于saved ,那么他就不属于循环等待的情况 // 因为模块状态为saved的时候代表该模块的信息已经被存储到了内部变量cacheModules内 if (!module || module.status !== STATUS.SAVED) { return false } //反之,将该依赖模块的uri信息将被推入到循环检查栈 circularCheckStack.push(module.uri) //获取依赖模块的依赖 var deps = module.dependencies //如果依赖模块没有对别的模块产生依赖,则直接返回 false if (deps.length) { // 如果依赖模块存在对别的模块有依赖 // 那么接下去将会在依赖关系中查找是否存在依赖uri的情形,存在返回true //util.indexOf:返回指定字符串在某个数组成员匹配中首次全文匹配的索引,如果没有匹配则返回 -1 if (util.indexOf(deps, uri) > -1) { return true } //如果不存在上述情形,那么进一步查看,依赖模块的依赖模块,查看他们是否存在对当前模块存在依赖,如果存在返回true //逐层检查 for (var i = 0; i < deps.length; i++) { if (isCircularWaiting(cachedModules[deps[i]], uri)) { return true } } //如果经过这两步的判断还是没有发现依赖,那就只能返回false了,认为没有循环依赖 return false } return false } //打印存在循环依赖 function printCircularLog(stack, type) { util.log('Found circular dependencies:', stack.join(' --> '), type) } // Public API // ---------- //创建一个全局模型对象 var globalModule = new Module(util.pageUri, STATUS.COMPILED) // 从配置文件读取是否有需要提前加载的模块 // 如果有预先加载模块,事先设置预加载模块为空,并加载预加载模块并执行回调,如果没有则顺序执行 seajs.use = function(ids, callback) { var preloadMods = config.preload if (preloadMods.length) { // Loads preload modules before all other modules. globalModule._use(preloadMods, function() { config.preload = [] globalModule._use(ids, callback) }) } else { globalModule._use(ids, callback) } return seajs } // For normal users // 针对于普通开发者 // define 是全局函数,用来定义模块。在开发时,define 仅接收一个 factory 参数。factory 可以是一个函数,也可以是对象、字符串等类型。 seajs.define = Module._define // 通过 seajs.cache 你可以看到 seajs 当前已经加载的所有模块信息。 seajs.cache = Module.cache // 通过 seajs.find,可以快速查到到 seajs.cache 中的特定模块。 seajs.find = Module._find // 在拿到 module.exports 对象、正式返回给模块系统前,我们可以做点手脚:对 module.exports 进行加工,这就是 seajs.modify 功能。 seajs.modify = Module._modify // For plugin developers // 为开发者提供插件 seajs.pluginSDK = { Module: Module, util: util, config: config } })(seajs, seajs._util, seajs._config) /** * The configuration */ ;(function(seajs, util, config) { //var noCachePrefix = 'seajs-ts=' //var noCacheTimeStamp = noCachePrefix + util.now() //为了消除seajs-ts (个人所加,为方便调试源码) var noCachePrefix = 'seajs-ts=' var noCacheTimeStamp = noCachePrefix + '1' // Async inserted script // 异步创建seajs节点 var loaderScript = document.getElementById('seajsnode') // Static script // 静态节点 如果不存在seajsnode, if (!loaderScript) { var scripts = document.getElementsByTagName('script') loaderScript = scripts[scripts.length - 1] } // 如果seajs为内联形式的,则把他的base路径设置为pageuri var loaderSrc = util.getScriptAbsoluteSrc(loaderScript) || util.pageUri // When sea.js is inline, set base to pageUri. var base = util.dirname(getLoaderActualSrc(loaderSrc)) util.loaderDir = base // When src is "http://test.com/libs/seajs/1.0.0/sea.js", redirect base // to "http://test.com/libs/" var match = base.match(/^(.+\/)seajs\/[\d\.]+\/$/) if (match) { base = match[1] } // 设置base路径 config.base = base // 获取data-main var dataMain = loaderScript.getAttribute('data-main') if (dataMain) { config.main = dataMain } // The default charset of module file. // 默认编码方式为 utf-8 config.charset = 'utf-8' /** * The function to configure the framework * config({ * 'base': 'path/to/base', * 'alias': { * 'app': 'biz/xx', * 'jquery': 'jquery-1.5.2', * 'cart': 'cart?t=20110419' * }, * 'map': [ * ['test.cdn.cn', 'localhost'] * ], * preload: [], * charset: 'utf-8', * debug: false * }) * */ seajs.config = function(o) { // 建立循环,逐一对比config中的属性 for (var k in o) { // 如果传入对象o中的属性k不是原型链的一部分,那么中断循环,进行下一次循环 if (!o.hasOwnProperty(k)) continue // 设定原始值 var previous = config[k] // 设定当前值 var current = o[k] // 如果原始值存在,并且其属性名为'alias' if (previous && k === 'alias') { // 那么循环取当前值 for (var p in current) { // 如果 current对象的原型链的一部分含有p if (current.hasOwnProperty(p)) { // 把原始值赋值给 preValue var prevValue = previous[p] // 当前值赋值给currValue var currValue = current[p] // Converts {jquery: '1.7.2'} to {jquery: 'jquery/1.7.2/jquery'} // 格式转换 if (/^\d+\.\d+\.\d+$/.test(currValue)) { currValue = p + '/' + currValue + '/' + p } //检查别名时候存在冲突 checkAliasConflict(prevValue, currValue, p) previous[p] = currValue } } } // 再者,如果原始值存在,并且k为map或者preload的时候 else if (previous && (k === 'map' || k === 'preload')) { // for config({ preload: 'some-module' }) // 事先判断current是否为字符串,如果是字符串类型将其转为数组 if (util.isString(current)) { current = [current] } // 遍历current的同时将其成员设置到原始config值中 util.forEach(current, function(item) { if (item) { previous.push(item) } }) } // 其他情况 将config中值直接用当前值替换 else { config[k] = current } } // Makes sure config.base is an absolute path. // 确保config.base 是一个绝对的路径 var base = config.base if (base && !util.isAbsolute(base)) { config.base = util.id2Uri('./' + base + '/') } // Uses map to implement nocache. // 在启用map的时候自动运行nocache机制,所谓nocache 就是给资源链接加时间戳 if (config.debug === 2) { config.debug = 1 seajs.config({ map: [ [/^.*$/, function(url) { if (url.indexOf(noCachePrefix) === -1) { url += (url.indexOf('?') === -1 ? '?' : '&') + noCacheTimeStamp } return url }] ] }) } debugSync() return this } // 设置seajs.debug值 // debug值为 true 时,加载器会使用 console.log 输出所有错误和调试信息。 默认为 false, 只输出关键信息。 // 另外,还可以将 debug 值设为 2 . 这种情况下, 每个脚本请求都会加上唯一时间戳。这在测试期间很有用,可以强制浏览器每次都请求最新版本,免去 Ctrl + F5 之烦恼。 function debugSync() { if (config.debug) { // For convenient reference seajs.debug = !!config.debug } } debugSync() // 获取Loader地址 function getLoaderActualSrc(src) { // 如果链接中不存在‘??’,那么直接返回 ,主要用于检查是否combo if (src.indexOf('??') === -1) { return src } // Such as: http://cdn.com/??seajs/1.2.0/sea.js,jquery/1.7.2/jquery.js // Only support nginx combo style rule. If you use other combo rule, please // explicitly config the base path and the alias for plugins. // 如果存在??的形式,依托与?? 对src进行字符串分离 (这种情形出现在具有combo功能的服务端) // 第一部分作为root根地址,其余部分,再通过‘,’进行字符串分离,并返回匹配为sea.js的结果 var parts = src.split('??') var root = parts[0] var paths = util.filter(parts[1].split(','), function(str) { return str.indexOf('sea.js') !== -1 }) // 返回loader地址 return root + paths[0] } // 检查别名冲突 function checkAliasConflict(previous, current, key) { if (previous && previous !== current) { util.log('The alias config is conflicted:', 'key =', '"' + key + '"', 'previous =', '"' + previous + '"', 'current =', '"' + current + '"', 'warn') } } })(seajs, seajs._util, seajs._config) /** * Prepare for debug mode */ ;(function(seajs, util, global) { // The safe and convenient version of console.log seajs.log = util.log // Creates a stylesheet from a text blob of rules. seajs.importStyle = util.importStyle // Sets a alias to `sea.js` directory for loading plugins. // 设置一个seajs的别名,其为了seajs加载插件而准备 seajs.config({ alias: { seajs: util.loaderDir } }) // Uses `seajs-debug` flag to turn on debug mode. if (global.location.search.indexOf('seajs-debug') > -1 || document.cookie.indexOf('seajs=1') > -1) { seajs.config({ debug: 2 }).use('seajs/plugin-debug') // Delays `seajs.use` calls to the onload of `mapfile`. seajs._use = seajs.use seajs._useArgs = [] seajs.use = function() { seajs._useArgs.push(arguments); return seajs } } })(seajs, seajs._util, this) /** * The bootstrap and entrances */ ;(function(seajs, config, global) { var _seajs = seajs._seajs // Avoids conflicting when sea.js is loaded multi times. // 避免多次引入seajs导致冲突 if (_seajs && !_seajs['args']) { global.seajs = seajs._seajs return } // Assigns to global define. // 把seajs的define方法注册要全局 global.define = seajs.define // Loads the data-main module automatically. // 自动加载data-main。 其实质还是seajs.use. data-main目前未明了bug数几个,所以不是很推荐使用它,推荐直接使用seajs.use()。 config.main && seajs.use(config.main) // Parses the pre-call of seajs.config/seajs.use/define. // Ref: test/bootstrap/async-3.html // 主要用于异步加载seajs时的处理方案 之下为玉伯原话: // 立刻调用 seajs 的接口时,sea.js 可能还处于下载中,还未下载下来 // 这时通过下面的内嵌代码,等于定义了一个假的 seajs // 这个假的 seajs 上,有 config / use / define 等方法 // 但调用时并没真正执行,仅仅将参数保存了起来,这样,等真正的 seajs 加载好了后就可以从这假的 seajs 中得到之前进行了怎样的调用,从而让调用生效 // 这是 seajs 里第一行代码的作用 seajs = { _seajs: this.seajs } // seajs = { _seajs: this.seajs } 这个还有个作用是,避免多次加载 sea.js 时的冲突 ;(function(args) { if (args) { var hash = { 0: 'config', 1: 'use', 2: 'define' } for (var i = 0; i < args.length; i += 2) { seajs[hash[args[i]]].apply(seajs, args[i + 1]) } } })((_seajs || 0)['args']) // Keeps clean! delete seajs.define delete seajs._util delete seajs._config delete seajs._seajs })(seajs, seajs._config, this)
相关推荐
seajs3.0 源码注释,只是本人阅读源码的一些心得及简单注释。 或者直接查看 博文 查看模块加载过程的流程解释。
Seajs是中国开源社区推出的一款浏览器端的模块加载器,它借鉴了CommonJS的规范,但针对浏览器环境进行了优化,使得JavaScript在浏览器端也能实现模块化的开发。本教程将带你快速了解并掌握Seajs的使用,让你在5分钟...
**Seajs 官方小实例详解** Seajs 是一个用于浏览器端的模块加载器,它遵循 CommonJS 规范,让 JavaScript 开发者能够在浏览器环境中实现模块化开发,提高代码的可维护性和复用性。Seajs 的核心理念是通过模块化解决...
SeaJS 是一款专为Web端设计的JavaScript模块加载器,它的出现是为了解决JavaScript在浏览器环境中的组织和管理问题。随着Web应用的复杂度不断提升,JavaScript代码的组织和依赖管理变得至关重要,SeaJS 提供了一种...
Seajs是中国开源社区非常受欢迎的一款JavaScript模块加载器,它的出现为Web开发引入了CommonJS规范,使得前端开发更加模块化,便于代码管理和维护。Seajs 2.3.0是该库的一个稳定版本,提供了丰富的功能和优化。 一...
Seajs是中国开源社区推出的一款基于模块化开发的前端加载器,它借鉴了CommonJS的模块化思想,但针对浏览器环境进行了优化。Seajs的核心理念是让JavaScript模块化变得简单,帮助开发者解决在大型Web项目中代码组织、...
Seajs是中国开源社区发展出来的一款轻量级的前端模块化加载器,它的出现是为了应对JavaScript在浏览器端组织和管理代码的复杂性。本手册将详细阐述Seajs的核心概念、使用方法以及如何通过它来构建高效的前端应用。 ...
此外,Seajs的源码结构清晰,注释丰富,是学习JavaScript编程思想和技巧的好材料。 在进行源码阅读时,可以结合提供的"seajs执行流程图.vsd"文件,这应该是一个Visio绘制的流程图,它可以帮助我们更直观地理解Seajs...
Seajs 是一个轻量级的前端模块加载器,它的出现是为了在浏览器端实现 CommonJS 规范,使得 JavaScript 的组织和开发变得更加模块化。Seajs 版本 2.3.0 是其稳定的一个版本,提供了更完善的特性和优化。 在...
Seajs-2.2.3 是一个开源的 JavaScript 模块加载器,旨在为开发者提供简单、极致的模块化开发体验。它强调的是简洁、自然的代码书写和组织方式,使得项目管理和协同工作变得更加高效。Seajs 在JavaScript社区中扮演着...
Seajs 是一个基于浏览器的模块加载器,它遵循 CommonJS 规范,使得在浏览器环境中可以实现模块化开发。这个“seajs入门完整案例”针对初学者提供了学习资源,帮助他们快速掌握Seajs的基本用法和特性。 1. **模块化...
Seajs是中国著名的前端模块加载器,它借鉴了CommonJS的规范,使得JavaScript在浏览器环境中也能实现模块化的开发。本文将深入解析Seajs 2.2.0版本的源码,探讨其核心机制、设计理念以及如何使用它来组织和管理前端...
这个框架的版本v0.9.1是一个免费版,适用于各种项目开发,帮助开发者更有效地组织和管理JavaScript代码,提高代码的可维护性和复用性。 SeaJS的核心理念是模块化,它借鉴了CommonJS标准,使得JavaScript代码可以像...
Seajs是中国开源社区发展出来的一款模块化加载器,它的出现是为了应对JavaScript在浏览器环境中缺乏一个标准的模块化解决方案的问题。Seajs的设计理念是基于CMD(Common Module Definition)规范,它鼓励开发者采用...
**spm-v1.1.2-with-seajs** `SPM v.1.1.2 With SeaJS` 是一个专门用于JavaScript模块管理和构建的工具,它结合了SeaJS库,为前端开发者提供了一种高效、方便的方式来组织和管理项目中的代码模块。在本文中,我们将...
CMD规范之Seajs Seajs是一款轻量级的前端模块加载器,它遵循CommonJS规范,使得JavaScript在浏览器端也能实现模块化的开发。这个规范主要包含模块定义、模块加载和模块化开发的思想,大大提高了代码的可维护性和...
SeaJS 是一个轻量级的前端模块加载器,它的出现是为了改善JavaScript在浏览器环境中的组织和加载方式。本文将深入探讨SeaJS的基本概念、工作原理以及如何通过一个完整的例子来理解其用法。 SeaJS的核心理念是遵循 ...
Seajs是一款轻量级的JavaScript模块加载器,它遵循CommonJS规范,旨在解决浏览器环境中的模块化问题。在深入理解seajs源代码之前,我们首先需要了解模块化的基本概念和CommonJS规范。 模块化是软件开发中的一种组织...
**SeaJS 和 RequireJS 是两种广泛使用的 JavaScript 模块加载器和依赖管理工具,它们的主要目的是解决 JavaScript 在浏览器端的异步加载和模块化问题。** **SeaJS** SeaJS 是一个轻量级的模块加载器,由中国淘宝...
"grunt-react-seajs"就是一个这样的工具,它是一个专门为前端开发者设计的Grunt插件,旨在帮助开发者将React组件转换为SeaJS模块,使得在不支持ES6模块语法的环境中也能顺利地使用React组件。下面我们将深入探讨这个...