只处理了两个模块直接循环依赖,还没有解决模块间间接循环依赖的问题。
(function (global) {
global.define = define
global.define.amd = {}
global.require = require
let _curDeps = [] // 记录 require 方法调用后,当前主模块的所有依赖(包含子模块的依赖)
const _modules = {} // 所有的模块信息
global._modules = _modules
/** * 定义一个模块 * @param {String} name - 模块名称(可省略) * @param {Array} deps - 依赖模块数组(可省略) * @param {Function} callback - 成功回调函数, 必须带return */
function define (name, deps, callback) {
// define([], function() {})
if (Array.isArray(name)) {
callback = deps
deps = name
} else if (typeof name === 'function') {
// define(function () {})
deps = []
callback = name
}
new Module(deps, helpers.modulePathToModuleName(helpers.getCurrentJsSrc()), callback)
}
/** * 入口文件的函数 * @param {Array} dep - 依赖模块数组(可省略) * @param {Function} cb - 成功回调函数 */
function require (dep, callback) {
_curDeps = [] // 每次调用前清空
let deps = typeof dep === 'string' ? [dep] : dep
let moduleName = helpers.getModuleName()
let allDeps = []
// 主模块
let mainModule = new Module(deps, moduleName, callback).loadDeps(onload)
// 每次依赖的模块加载完成都会执行
function onload (module) {
allDeps.push(module.name)
// 当数量一致时表示已经加载完主模块的所有依赖
if (_curDeps.length === allDeps.length) {
// 执行所有依赖模块的接口函数
allDeps.forEach(moduleName => {
_modules[moduleName].execute()
})
// 执行主模块的接口函数
setTimeout(() => {
mainModule.execute()
}, 0)
}
}
}
/** * 模块对象 * @param {Array} deps - 依赖数组 * @param {String} name - 模块名 * @param {Function} callback - 模块接口函数 */
function Module (deps, name, callback) {
this.deps = deps
this.name = name
this.callback = callback
// 将每个模块实例存储在_modules对象上
if (!_modules[this.name]) {
_modules[this.name] = this
}
return this
}
Module.STATUS = {
LOADED: 2, // 加载成功
ERROR: 5, // 加载失败
}
// 加载模块依赖文件(包含子模块的依赖)
Module.prototype.loadDeps = function (callback) {
this.deps.forEach(moduleName => {
// 依赖模块还没有在当前主模块依赖中则加载(防止重复加载)
if (_curDeps.indexOf(moduleName) === -1) {
_curDeps.push(moduleName)
// 如果还没有加载过
if (!_modules[moduleName]) {
helpers.loadJS(helpers.moduleNameToModulePath(moduleName), () => {
let module = _modules[moduleName]
// 加载子模块的依赖文件
module.loadDeps(callback)
// 加载成功
module.status = 2
// 循环依赖检查
helpers.checkDeps(moduleName)
// 加载完成回调
callback && callback(module)
}, () => {
let module = _modules[moduleName]
// 加载失败
module.status = 5
})
} else {
// 已经加载过,直接执行回调
callback && callback(_modules[moduleName])
}
}
})
return this
}
// 执行模块的回调函数
Module.prototype.execute = function () {
let params = []
if (typeof this.exports !== 'undefined') return
// 遍历所有依赖模块
this.deps.forEach(moduleName => {
let module = _modules[moduleName]
// 递归调用
module.execute()
params.push(module.exports)
})
// 执行函数,得到模块接口
if (typeof this.callback === 'function') {
this.exports = this.callback.apply(global, params)
}
}
// 协助函数
const helpers = {
/** * 加载JS文件到页面上 * @param {String} src - JS文件绝对路径 * @param {Function} callback - 成功回调 */
loadJS (src, callback, errorCallback) {
let script = document.createElement('script')
script.async = true
script.onload = callback || function () { }
script.onerror = errorCallback || function () { console.warn(`${src}加载失败`) }
script.src = src
document.getElementsByTagName('head')[0].appendChild(script)
},
// 获取当前运行的js文件路径
getCurrentJsSrc () {
return document.currentScript && document.currentScript.src
},
// 获取当前运行的JS element
getCurrentJS () {
return document.currentScript
},
/** * 将模块名转换成模块路径 * @param {String} name - 模块名 * @returns {String} - 模块路径 */
moduleNameToModulePath (name) {
let reg = /\w*.js/
let output = reg.exec(name)
if (!output) {
return `./${name}.js`
} else {
return name
}
},
/** * 将模块的路径转换成模块名 * @param {String} path - 模块路径 * @returns {String} - 模块名 */
modulePathToModuleName (path) {
let reg = /\w*.js/
let output = reg.exec(path)
if (!output) {
return path
} else {
return output[0].split('.')[0]
}
},
/** * 获取模块名,没有的话随机分配一个 */
getModuleName () {
return this.getCurrentJsSrc() ? this.modulePathToModuleName(this.getCurrentJsSrc()) : ~~new Date()
},
/** * 检查模块之间是否出现循环依赖(只处理了两个模块直接的依赖,间接相互依赖没有处理) */
checkDeps (curModuleName) {
for (moduleName in _modules) {
if (_modules[curModuleName].status !== 2) break
if (curModuleName !== moduleName && _modules[moduleName].status === 2) {
//当前模块在遍历模块的依赖里有出现 && 遍历模块的依赖有出现当前模块
if (
_modules[moduleName].deps.indexOf(curModuleName) !== -1 &&
_modules[curModuleName].deps.indexOf(moduleName) !== -1
) {
throw Error(`${curModuleName}和${moduleName}存在循环依赖`)
}
}
}
}
}
/** * 主入口,如果有指定data-main属性则执行 */
function entry () {
let moduleName = helpers.getCurrentJS().getAttribute('data-main')
if (moduleName) {
helpers.loadJS(helpers.moduleNameToModulePath(moduleName))
}
}
entry()
})(this)