一年前,刚来网易实习的时候接触了NEJ,那是第一次接触模块化开发,感受想出这个idea的人简直是天才,同时也对于这种框架的实现很是好奇,惭愧的是,那时甚至连jQuery的原理都不知道。html
随着这一年对于JS面向对象的理解有所加深,看着《JavaScript设计模式》就跟着本身动手码码代码,因此这是一篇读书笔记,并非发明创造,而且这个加载器是比较简陋的,颇有改进空间。json
模块采用的是匿名模块,它的js绝对路径做为它的唯一标识:设计模式
define([ '{lib}dom', '{pro}extend' ], function(dom, extend) { //TODO })
从上面咱们能够看出,模块是由define函数来定义,传入参数为:依赖列表和回调函数,为了实现依赖注入,要等到依赖列表的全部js加载完后再来执行回调函数。缓存
因此第一步,咱们循环遍历依赖列表,而后依次加载列表的模块,可想而知,在循环遍历加载模块的代码的结构应该是下面这样子的:app
//modules = ['lib/dom.js', 'js/extend.js'] var modCount = modules.length; var params = []; //保存依赖列表的对象 for (var i = 0, len = modules.length; i < len; i++) { (function(i){ var url = modules[i]; loadModule(url, function(module) { modCount--; params[i] = module; if (modCount == 0) { defineModule(uid, params, callback); //uid为该模块绝对路径,callback为传入的回调函数 } }) })(i) }
上面的代码只是部分代码,可是咱们能够很清楚地看到思路就是循环加载模块,同时传入一个回调,加载完成后触发回调,回调函数里会将modCount(模块个数)减1,若是modCount变为0,那么说明就所有模块都加载完成了,就执行defineModule函数,同时传入所有的依赖对象。框架
要触发回调,首先要知道何时js脚本何时加载完成。咱们建立一个script标签,append进body,这样就能够加载js脚本,那么何时脚本加载完成呢?dom
有的人可能立刻就想到了,当js代码开始执行的时候就说明这个脚本加载完了。注意,只是这个脚本,不要忘记在这个脚本当中,咱们可能还依赖了其余模块,这样咱们还要等待这个依赖模块加载完它所拥有的依赖模块列表后执行其回调函数才算这个模块加载完成。异步
因此这样子咱们能够知道最终的加载完成的标志就是执行defineModule函数,因此在loadModule函数中,咱们须要将加载回调函数进行缓存,等待后面加载完成后执行。async
//moduleCache = {} 是定义在全局的一个模块缓存对象 function loadModule(uid, callback) { var _module; if (moduleCache[uid]) { _module = moduleCache[uid]; if (_module.status == 'loaded') { setTimeout(callback(_module.exports), 0); } else { _module.onload.push(callback); } } else { moduleCache[uid] = { uid: uid, status: 'loading', exports: null, onload: [callback] }; loadScript(uid); } } function loadScript(url) { var _script = document.createElement('script'); _script.charset = 'utf-8'; _script.async = true; _script.src = url; document.body.appendChild(_script); }
上面代码的思路是加载模块的时候,先在缓存对象中寻找看看有没有存在的模块。ide
到这里,咱们能够感受到快要写完了,可是咱们仍然没有执行加载模块后的回调函数,上面也交代了,模块加载完成后总会执行defineModule函数,因此在这里执行回调,上代码:
function defineModule(uid, params, callback) { if (moduleCache[uid]) { var _module = moduleCache[uid]; _module.status = 'loaded'; _module.exports = callback ? callback.apply(_module, params) : null; while (fn = _module.onload.shift()) { fn(_module.exports); } } else { moduleCache[uid] = { uid: uid, status: 'loaded', onload: [], exports: callback && callback.apply(null, params) } } }
能够看到,定义模块时咱们判断是否存在,若是存在,说明这个模块是被依赖的,因此就执行onload里缓存的回调函数。
上面就把功能实现了,可是仍是有很多问题的,好比依赖列表的js路径问题,uid怎么获取,还有可能须要加载html文件等等,可是这些都是一些小问题,总体模块加载器已经完成,剩下的就是修修补补,下面附上我目前的define.js文件代码:
(function(win, doc){ var moduleCache = {}; var t = /(\S+)define\.js(?:\?pro=(\S+))?/.exec(getCurrentUrl()), lib = t[1], pro = t[2] || '/', dir = win.location.href; var tReg = /^\.\/|^\//; while (tReg.test(pro)) { pro = pro.replace(tReg, '') } var backCount = 0; tReg = /^\.\.\//; while (tReg.test(pro)) { backCount++; pro = pro.replace(tReg, '') } pro = backUrl(lib, backCount) + pro; var tplReg = /\.html$/; function getCurrentUrl(){ return document.currentScript.src; } function backUrl(url, count) { for (var i = 0; i < count; i++) { url = url.replace(/[^/]+\/?$/, ''); } return url; } function fixUrl(url) { if (tplReg.test(url)) { if (/^\{lib\}/.test(url)){ return url.replace(/^\{lib\}/, lib); } else if (/^\{pro\}/.test(url)) { return url.replace(/^\{pro\}/, pro); } else { return url; } } return url.replace(/^\{lib\}/, lib).replace(/^\{pro\}/, pro).replace(/\.js$/g, '') + '.js'; } function loadScript(url) { var _script = document.createElement('script'); _script.charset = 'utf-8'; _script.async = true; _script.src = fixUrl(url); document.body.appendChild(_script); } function defineModule(uuid, mParams, callback) { if (moduleCache[uuid]) { var _module = moduleCache[uuid]; _module.status = 'loaded'; _module.exports = callback ? callback.apply(_module, mParams) : null; while (fn = _module.onload.shift()) { fn(_module.exports); } } else { moduleCache[uuid] = { uuid: uuid, status: 'loaded', exports: callback && callback.apply(null, mParams), onload: [] } } } function loadModule(uuid, callback) { var _module; if (moduleCache[uuid]) { _module = moduleCache[uuid]; if (_module.status == 'loaded') { setTimeout(callback(_module.exports), 0); } else { _module.onload.push(callback); } } else { moduleCache[uuid] = { uuid: uuid, status: 'loading', exports: null, onload: [callback] }; loadScript(uuid); } } var define = function(modules, callback) { modules = Array.isArray(modules) ? modules : []; for (var i = 0, len = modules.length; i < len; i++) { modules[i] = fixUrl(modules[i]); } var uuid = getCurrentUrl(), mlen = modules.length, mParams = [], i = 0, loadCount = 0; if (mlen) { while (i < mlen) { loadCount++; (function(i){ if (tplReg.test(modules[i])) { loadText(modules[i], function(_json){ var tpl = ''; if (_json.code == 200) { tpl = _json.result; } loadCount--; mParams[i] = tpl; if (loadCount == 0) { defineModule(uuid, mParams, callback); } }) } else { loadModule(modules[i], function(module) { loadCount--; mParams[i] = module; if (loadCount == 0) { defineModule(uuid, mParams, callback); } }); } })(i); i++; } } else { defineModule(uuid, [], callback) } } function loadText(url, callback) { var xhr = new XMLHttpRequest(); xhr.open("get", url, true); xhr.send(null); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { var code = 200; } else { code = xhr.status; } callback({ code: code, result: xhr.responseText }) } } } loadScript(fixUrl('{lib}router')); win.define = define; win.gObj = { loadScript: loadScript, loadText: loadText, lib: lib, pro: pro, fixUrl: fixUrl } })(window, document)
这个加载器目前我知道的问题有:
没法处理循环依赖的问题,也就是a依赖b,b再依赖a,并不会报错。
获取js路径函数没有作兼容处理,在IE上并不能这么获取
代码写得比较糙,至少在路径上处理能够作优化