SeaJS v1.2 中文注释版
soda-x opened this issue · 17 comments
/**
* @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)
感谢 @pigcan !
用了心的注释,就是不一样!
已加入到文档页中 http://seajs.org/docs/#articles
源码前面用分号打头,有什么特别的意思吗?
一般每一段js后面不都是加; 号的吗? 为什么上面代码要加在前面呢?
如;(function(util) {}
分号的问题,可以看这篇文章:
http://www.zhihu.com/question/20298345
2012/10/16 alan-hjkl notifications@github.com
源码前面用分号打头,有什么特别的意思吗?
一般每一段js后面不都是加; 号的吗? 为什么上面代码要加载前面呢?
如;(function(util) {}—
Reply to this email directly or view it on GitHubhttps://github.com//issues/305#issuecomment-9473704.
王保平 / 玉伯(射雕)
送人玫瑰手有余香
避免多个文件压缩合并之后本代码出现bug,因为上一个文件会跟这一个文件的内容连起来,没有了换行符,所以很可能出现意料之外的事情
On Oct 16, 2012, at 1:43 PM, alan-hjkl wrote:
源码前面用分号打头,有什么特别的意思吗?
一般每一段js后面不都是加; 号的吗? 为什么上面代码要加载前面呢?
如;(function(util) {}—
Reply to this email directly or view it on GitHub.
谢谢二位的回答,这种无分号,或是少分号的方式,代码看起来简洁多啦。
util.forEach 自定义实现的方法有点不明白,
网上查阅资料显示这是js 1.6的新特性,是让数组中的每一个元素都执行fn方法。。
照此推的话,那么数组中每一元素都应该是函数名了。执行的方法应该是arr[i].fn( ) 啦
为什么上面的例子中 用 fn(arr[i], i, arr) 呢?
不是的,arr[i] 是普通数组元素,不一定是函数
2012/10/18 alan-hjkl notifications@github.com
util.forEach 自定义实现的方法有点不明白,
网上查阅资料显示这是js 1.6的新特性,是让数组中的每一个元素都执行fn方法。。
照此推的话,那么数组中每一元素都应该是函数名了。执行的方法应该是arr[i].fn( ) 啦
为什么上面的例子中 用 fn(arr[i], i, arr) 呢?
—
Reply to this email directly or view it on GitHubhttps://github.com//issues/305#issuecomment-9558374.
王保平 / 玉伯(射雕)
送人玫瑰手有余香
Mark
mark
mark
mark
mark
mark
util.isObject 为什么不用 toString.call(val) === '[object object]' 而是使用 val === Object(val) 是有什么别的考虑嘛??
确实不错
util.isObject 为什么不用 toString.call(val) === '[object object]' 而是使用 val === Object(val) 是有什么别的考虑嘛??
JavaScript中基本类型有: null, undefined, number, string, boolean; 引用类型有: Object对象(又叫做plain object),Array数组,Date日期对象,Function函数,以及任意自己构造的自定义class类型。
我们知道:
- 通过 typeof 判断类型时,null也会返回 'object',所以他无法用来区分 _
val是否是引用类型还是基本类型null
; 因此无法实现判断一个变量是引用类型还是基本类型
,除非你额外对null和function进行条件判断。 - 而通过 Object.prototype.toString.call() 的方式可以获得一个任意类型变量的精确类型名称,却也无法直接实现
判断一个变量是基本类型还是引用类型
,除非你把所有引用类型的名称都枚举出来。 - 而 Object(val) 这个语法,是对val变量进行引用类型包装。经过试验发现,所有基本类型都可以自动包装成其对应的一个引用类型,而引用类型变量经过包装后还是自身。所以通过 val === Object(val) 便可以判断val是引用类型还是基本类型。
@berwin 我认为这里isObject函数是用作 判断val是否是引用类型
,而不是判断是val是否是精确的plain object。
这里 util.unique数组去重写法也有点问题。这种算法会导致去重后 结果数组中的元素全部变成 数组元素.toString()
的值。