动手实现一个 AMD 模块加载器(三)
在上一篇文章中,我们的 AMD 模块加载器基本已经能够使用了,但是还不够,因为我们没有允许匿名模块,以及没有依赖等情况。实际上在 amd 的规范中规定的就是 define 函数的前两个参数是可选的,当没有 id(模块名)的时候也就意味着不会有模块依赖于这个模块。很显然,我们的 define 函数的每个参数的类型是不同的,因此我们需要一些函数来做类型判断,如下:
function isFun(f) { return Object.prototype.toString.call(f).toLowerCase().indexOf('function') > -1; } function isArr(arr) { return Array.isArray(arr); } function isStr(str) { return typeof str === 'string'; }
将这些类型判断函数运用在 define 函数,判断这个模块是否有依赖,是否为匿名模块,这是一个比较简单的工作,修改 define 函数如下:
function define(name, deps, callback) { if(!isStr(name)) { callback = deps; deps = name; name = null; } if(!isArr(deps)) { callback = deps; deps = []; } if(moduleMap[name]) { name=moduleMap[name] } name = replaceName(name); deps = deps.map(function(ele, i) { return replaceName(ele); }); modMap[name] = modMap[name] || {}; modMap[name].deps = deps; modMap[name].status = 'loaded'; modMap[name].callback = callback; modMap[name].oncomplete = modMap[name].oncomplete || []; }
进行一次测试,不过在测试之前,我们需要知道的是,我们将匿名模块的 name 修改为了 null,而后面有一个 replaceName 方法是做 name 替换的,这里没有判断 name 是否为 null 的情况,因此需要在开头做一次判断,增加如下代码:
function replaceName(name) { if(name===null) { return name; } // ...... }
测试代码如下:
loadjs.config({ baseUrl:'./static', paths: { app: './app' } }); loadjs.define('cc',['a'], function(a) { console.log(1); console.log(a.add(1,2)); }); loadjs.define('ab', function() { console.log('ab'); }); loadjs.define(function() { console.log('unknow'); }); loadjs.use(['ab','cc'],function() { console.log('main'); });
测试结果如下:
说明正确。此时我们的一个简单的amd模块加载器就这样写完了,删除 console 增加注释就可以比较好的使用了,最后整理一下代码如下:
(function(root){ var modMap = {}; var moduleMap = {}; var cfg = { baseUrl: location.href.replace(/(/)[^/]+$/g, function(s, s1){ return s1 }), path: { } }; // 完整网址 var fullPathRegExp = /^[(https?://) | (file:///)]/; // 局对路径 var absoPathRegExp = /^//; // 以./开头的相对路径 var relaPathRegExp = /^.//; // 以../开头的的相对路径 var relaPathBackRegExp = /^..//; function isFun(f) { return Object.prototype.toString.call(f).toLowerCase().indexOf('function') > -1; } function isArr(arr) { return Array.isArray(arr); } function isStr(str) { return typeof str === 'string'; } function merge(obj1, obj2) { if(obj1 && obj2) { for(var key in obj2) { obj1[key] = obj2[key] } } } function outputPath(baseUrl, path) { if (relaPathRegExp.test(path)) { if(/..//g.test(path)) { var pathArr = baseUrl.split('/'); var backPath = path.match(/..//g); var joinPath = path.replace(/[(^./)|(../)]+/g, ''); var num = pathArr.length - backPath.length; return pathArr.splice(0, num).join('/').replace(//$/g, '') + '/' +joinPath; } else { return baseUrl.replace(//$/g, '') + '/' + path.replace(/[(^./)]+/g, ''); } } else if (fullPathRegExp.test(path)) { return path; } else if (absoPathRegExp.test(path)) { return baseUrl.replace(//$/g, '') + path; } else { return baseUrl.replace(//$/g, '') + '/' + path; } } function replaceName(name) { if(name===null) { return name; } if(fullPathRegExp.test(name) || absoPathRegExp.test(name) || relaPathRegExp.test(name) || relaPathBackRegExp.test(name)) { return outputPath(cfg.baseUrl, name); } else { var prefix = name.split('/')[0] || name; if(cfg.paths[prefix]) { if(name.split('/').length === 0) { return cfg.paths[prefix]; } else {; var endPath = name.split('/').slice(1).join('/'); return outputPath(cfg.paths[prefix], endPath); } } else { return outputPath(cfg.baseUrl, name); } } } function fixUrl(name) { return name.split('/')[name.split('/').length-1] } function config(obj) { if(obj){ if(obj.baseUrl) { obj.baseUrl = outputPath(cfg.baseUrl, obj.baseUrl); } if(obj.paths) { var base = obj.baseUrl || cfg.baseUrl; for(var key in obj.paths) { obj.paths[key] = outputPath(base, obj.paths[key]); } } merge(cfg, obj); } } function use(deps, callback) { if(deps.length === 0) { callback(); } var depsLength = deps.length; var params = []; for(var i = 0; i < deps.length; i++) { moduleMap[fixUrl(deps[i])] = deps[i]; deps[i] = replaceName(deps[i]); (function(j){ loadMod(deps[j], function(param) { depsLength--; params[j] = param; if(depsLength === 0) { callback.apply(null, params); } }) })(i) } } function loadMod(name, callback) { /*模块还未定义*/ if(!modMap[name]) { modMap[name] = { status: 'loading', oncomplete: [] }; loadscript(name, function() { use(modMap[name].deps, function() { execMod(name, callback, Array.prototype.slice.call(arguments, 0)); }) }); } else if(modMap[name].status === 'loading') { // 模块正在加载 modMap[name].oncomplete.push(callback); } else if (!modMap[name].exports){ //模块还未执行完 use(modMap[name].deps, function() { execMod(name, callback, Array.prototype.slice.call(arguments, 0)); }) }else { callback(modMap[name].exports); } } function execMod(name, callback, params) { var exp = modMap[name].callback.apply(null, params); modMap[name].exports = exp; callback(exp); execComplete(name); } function execComplete(name) { for(var i = 0; i < modMap[name].oncomplete.length; i++) { modMap[name].oncomplete[i](modMap[name].exports); } } function loadscript(name, callback) { var doc = document; var node = doc.createElement('script'); node.charset = 'utf-8'; node.src = name + '.js'; /*为每个模块添加一个随机id*/ node.id = 'loadjs-js-' + (Math.random() * 100).toFixed(3); doc.body.appendChild(node); node.onload = function() { callback(); } } function define(name, deps, callback) { /*匿名模块*/ if(!isStr(name)) { callback = deps; deps = name; name = null; } /*没有依赖*/ if(!isArr(deps)) { callback = deps; deps = []; } if(moduleMap[name]) { name=moduleMap[name] } name = replaceName(name); /*对每个依赖名进行路径替换*/ deps = deps.map(function(ele, i) { return replaceName(ele); }); modMap[name] = modMap[name] || {}; modMap[name].deps = deps; modMap[name].status = 'loaded'; modMap[name].callback = callback; modMap[name].oncomplete = modMap[name].oncomplete || []; } var loadjs = { define: define, use: use, config: config }; root.define = define; root.loadjs = loadjs; root.modMap = modMap; })(window);
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Shell 编程快速入门指南
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论