seajs源码

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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值