模块化就是把系统分离成独立功能的方法,这样我们需要什么功能,就加载什么功能。
主要解决的问题:
- 命名冲突
- 文件依赖
代码学习:
define(function(require, exports, module) {
var age = 30;
var b = require('b.js');
console.log(b.sex);
exports.age = age;
});
define(function(require, exports, module){
var sex = '男';
return {
sex: sex
};
});
<script src="startUp.js"></script>
<script>
startUp.use(['a.js', 'b.js'], function(a, b) {
console.log(a);
console.log(b);
console.log('startUp...');
});
</script>
(function(global) {
startUp = global.startUp = {
version: '1.0.1'
};
var data = {};
var cache = {};
var anonymousMeta = {};
var status = {
FETCHED: 1, // 正在获取当前模块的uri
SAVED: 2, // 在缓存中存储模块信息
LOADING: 3, // 正在加载当前模块的依赖项
LOADED: 4, // 准备执行当前模块
EXECUTING: 5, // 正在执行单签模块
EXECUTED: 6 // 执行完毕已经获取到接口对象
};
var isArray = function(obj){
return toString.call(obj) === '[object Array]';
};
var isFunction = function(obj){
return toString.call(obj) === '[object Function]';
};
var isString = function(obj) {
return typeof obj === 'string';
};
// 是否使用了别名
function parseAlias(id) {
var alias = data.alias; //别名配置
return alias && isString(alias[id]) ? alias[id] : id;
}
//不能以 "/" 或":"开头,结尾必须是一个“/”后面跟随任意字符至少一个
var PATHS_RE = /^([^\/:]+)(\/.+)$/;
// 是否使用了路径短名称
function parsePaths(id) {
var paths = data.paths;
if(paths && (m = id.match(PATHS_RE)) && isString(paths[m[1]])) {
id = paths[m[1]] + m[2];
}
return id;
}
// 检测是否添加后缀
function normalize(path) {
var last = path.length - 1;
var lastC = path.charAt(last);
return (lastC === '/' || path.substring(last-2) === '.js') ? path : path + '.js';
}
// 添加根目录
function addBase(id, uri) {
var result;
if (id.charAt(0) === '.') {
result = relapath((uri ? uri.match(/[^?]*\//)[0] : data.cwd) + id);
}
else {
result = data.cwd + id;
}
return result;
}
var DOT_RE = /\/.\//g; // 规范路径 "/./" => "/"
// 相对路径的处理
function relapath(path) {
path = path.replace(DOT_RE, '/');
return path;
}
// 生成绝对路径
startUp.resolve = function(child, parent){
if (!child) {
return '';
}
child = parseAlias(child); // 处理别名
child = parsePaths(child); // 处理路径别名,路径短名称
child = normalize(child); // 是否添加后缀
return addBase(child, parent); // 添加根目录
};
//加载依赖项
startUp.request = function(url, callback){
var node = document.createElement('script');
node.src = url;
document.body.appendChild(node);
node.onload = function() {
// 移除script元素,不留加载痕迹
//node.onload = null;
//document.body.removeChild(node);
callback();
};
};
// Module 构造函数,模块初始化数据
function Module(uri, deps){
this.uri = uri;
this.deps = deps || [];
this.exports = null;
this.status = 0;
this._waitings = {};
this._remain = 0;
}
//分析主干上的依赖项
Module.prototype.load = function() {
var module = this;
module.status = status.LOADING;
var uris = module.resolve();
var len = module._remain = uris.length;
// 加载主干上的依赖项
var m;
for (var i = 0; i < len; i++) {
//创建缓存信息
m = Module.get(uris[i]);
if (m.status < status.LOADED) {
m._waitings[module.uri] = m._waitings[module.uri] || 1;
}
else {
module._remain--;
}
}
// 如果依赖项列表全部加载完毕
if (module._remain == 0) {
module.onload();
}
//准备执行根目录下的依赖项列表中的模块
var requestCache = {};
for(var i = 0; i < len; i++) {
// 获取依赖模块
m = Module.get(uris[i]);
if (m.status < status.FETCHED) {
m.fetch(requestCache);
}
}
for(uri in requestCache) {
requestCache[uri]();
}
};
// 加载依赖列表中的模块
Module.prototype.fetch = function(requestCache){
var module = this;
module.status = status.FETCHED;
var uri = module.uri;
requestCache[uri] = sendRequest;
function sendRequest() {
startUp.request(uri, onRequest);
}
function onRequest() {
if(anonymousMeta) {
module.save(uri, anonymousMeta);
}
// 递归,模块加载策略
module.load();
}
};
Module.prototype.onload = function() {
var mod = this;
mod.status = status.LOADED;
if (mod.callback) {
mod.callback();
}
_waitings = mod._waitings;
var uri, m;
for (uri in _waitings) {
//根目录对应的Module实例对象
m = cache[uri];
m._remain -= _waitings[uri];
if(m._remain == 0) {
m.onload();
}
}
};
// 更改初始化数据
Module.prototype.save = function(uri, meta) {
var module = Module.get(uri);
module.id = uri;
module.deps = meta.deps || [];
module.factory = meta.factory;
module.status = status.SAVED;
};
// 获取模块对外接口对象
Module.prototype.exec = function(){
var module = this;
//防止重复执行
if(module.status >= status.EXECUTING) {
return module.exports;
}
module.status = status.EXECUTING;
var uri = module.uri;
function require(id) {
// 更新过后的数据
console.log(require.resolve(id));
// 获取接口对象
return Module.get(require.resolve(id)).exec();
}
require.resolve = function(id){
return startUp.resolve(id, uri);
};
var factory = module.factory;
var exports = isFunction(factory) ? factory(require, module.exports = {}, module) : factory;
if(exports === undefined) {
exports = module.exports;
}
module.exports = exports;
module.status = status.EXECUTED;
return exports;
};
//资源定位, 解析依赖项生成绝对路径
Module.prototype.resolve = function(){
var mod = this;
var ids = mod.deps; // ['a.js','b.js'] => ['./a','./b']
var uris = [];
for(let i = 0; i < ids.length; i++) {
uris[i] = startUp.resolve(ids[i], mod.uri);
}
return uris;
};
//定义一个模块
Module.define = function(factory){
var deps;
if(isFunction(factory)) {
// 正则解析依赖项
deps = parseDependencies(factory.toString());
}
// 存储当前模块信息
var meta = {
id: "",
uri: "",
deps: deps,
factory: factory
};
anonymousMeta = meta;
}
// 检测缓存对象上是否有当前模块的信息
Module.get = function(uri, deps){
return cache[uri] || (cache[uri] = new Module(uri, deps));
};
Module.use = function(deps, callback, uri){
//获取模块
var module = Module.get(uri, isArray(deps) ? deps : [deps]);
// 所有模块都加载完毕
module.callback = function(){
// 所有依赖项模块的接口对象
var exports = [];
var uris = module.resolve();
for (var i = 0; i < uris.length; i++) {
exports[i] = cache[uris[i]].exec();
}
if (callback) {
callback.apply(global, exports);
}
};
module.load();
};
var _cid = 0;
function cid(){
return _cid++;
}
data.preload = [];
//获取当前项目文档的uri
data.cwd = document.URL.match(/[^?]*\//)[0];
Module.preload = function(callback) {
var length = data.preload.length;
if (!length) {
callback();
}
//length !== 0, 先加载预先设定模块
};
startUp.use = function(list, callback) {
// 检测有没有预先加载的模块
Module.preload(function() {
Module.use(list, callback, data.cwd + '_use_' + cid()); //虚拟根目录
});
};
var REQUIRE_RE = /\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g;
function parseDependencies(code) {
var ret = [];
code.replace(REQUIRE_RE, function(m, m1, m2){
if(m2){
ret.push(m2);
}
});
return ret;
}
global.define = Module.define;
})(this);