我的模块加载系统 v3
近一年来,外国非常热衷异步加载的研究。为了加快页面的载入速度,无阻塞的动态script注入的应用重新回到舞台的中心。LAB.js,ControlJS ,Head JS, yepnopejs, $script.js,NBL JohnnyDepp.js loadrunner还有基于jquery的RequireJS,嘛,它已是另一个层面的东西,还搞了一个术语AMD(Modules/AsynchronousDefinition)来唬人。国内的,暂时只有岁月如歌的 SeaJS 比较有名。说是顺应潮流也好,跟风凑热闹也好,我也升级了我的模块加载系统,取名为并行加载器,作为我的框架的核心模块而存在。
为方便起见,用定时器进行哈希检测代替本来需要的script回调函数验证。require,declare与provide这些方法名听岁月如歌说的某一套标准制定的东西。嘛,我单是觉得名字好用借来用用,并不遵循它的条规行事的。
下面是它的一些API介绍:
- dom.require(dependList,callback):请求模块。
- dependList为依赖列队,可以为字符串或数组。如果在字符串状态下表示多个模块,请用逗号隔开。如果依赖模块的JS文件与核心模块的JS文件不在同一个文件夹下,可以使用“/”隔开,或者后面用小括号指定具体路径,亦即"path/path/moduleName(url),path/path/moduleName(url)"的格式,可以简化为"moduleName(url)", "path/path/moduleName"。moduleName可以包含"."号。callback为回调函数,没有什么好说的。
- dom.provide(array,hash,callback):提供模块。
- array为数组,里面为经过处理的模块名,换言之,不再包含path或url。hash,用于注册与验证模块名,标识这个模块已经加载过了。callback为回调函数。
- dom.declare(moduleName,dependList,callback):声明模块。
- 此方法只是dom.require方法的一个简单封简而已,用于保证其回调函数在依赖模块都加载的情况下安全执行。moduleName为模块名,不能有path与url,dependList、callback这两个参数与dom.require相同。
- 按需加载
- 无阻塞
- 实现JS颗粒化管理
- 解决Ajax无法处理的跨域问题
- 模块自动处理依赖
- 以后还考虑文件合并的问题
作为约定,模块名应该与其所在的JS文件名相同,但也允许不一样(到时请用小括号指定具体路径),一个JS文件也可以放置多个模块,这时我抛弃使用script.onload与script.onreadystatechange来验证模块加载成功的原因,它们只在移除script节点本身时发挥效力。
王婆卖瓜,自吹一下优点:
;;;( function (WIN,DOM,undefined){ var reg_module_name = /(?:^|\/)([^(\/]+)(?=\(|$)/, reg_module_url = /\(([^)]+)\)/, reg_multi_module = /\s*,\s*/g, _dom = WIN.dom, dom = { mix : function (target, source ,override) { var i, ride = (override === void 0) || override; for (i in source) { if (ride || !(i in target)) { target[i] = source[i]; } } return target; }, noConflict: function ( ) { //防止命名冲突,请先行引用其他库再引用本框架 WIN.dom = _dom; //这是别人的 return dom; //请赋以其一个命名空间 }, //请求模块 require: function (dependList,callback){ var self = arguments.callee var moduleNames = [], i =0, hash = self.loadedModules, re = reg_multi_module, reg = reg_module_name ,moduleName, str; if ( typeof dependList === "string" ){ dependList = dependList.split(re) } while (str = dependList[i++]){ moduleName = str.match(reg)[1]; if (!hash[moduleName]){ moduleNames.push(moduleName); self.appendScript(str); } } this .provide(moduleNames,hash,callback) }, //声明模块 declare: function (moduleName,dependList,callback){ var hash = this .require.loadedModules; this .require(dependList, function (){ callback(); hash[moduleName] = 1 }) }, //提供模块 provide: function (array,hash,callback){ var flag = true , i = 0, args = arguments, fn = args.callee, el; while (el = array[i++]){ if (!hash[el]){ flag = false ; break ; } } if (flag){ callback(); } else { //javascrpt最短时钟间隔为16ms,这里取其倍数 //http://blog.csdn.net/aimingoo/archive/2006/12/21/1451556.aspx setTimeout( function (){ fn.apply( null ,args) },32); } } } dom.mix(dom.require, { loadedModules:{}, requestedUrl: {}, //http://www.cnblogs.com/rubylouvre/archive/2011/02/10/1950940.html getBasePath: function (){ var url; try { a.b.c() } catch (e){ url = e.fileName || e.sourceURL; //针对firefox与safari } if (!url){ var script = ( function (e) { if (!e) return null ; if (e.nodeName.toLowerCase() == 'script' ) return e; return arguments.callee(e.lastChild) })(DOM); //取得核心模块所在的script标签 url = script.hasAttribute ? script.src : script.getAttribute( 'src' , 4); } url = url.substr( 0, url.lastIndexOf( '/' )); dom.require.getBasePath = function (){ return url; //缓存结果,第一次之后直接返回,再不用计算 } return url; }, appendScript: function (str){ var module = str, reg = reg_module_url, url; //处理dom.node(http://www.cnblogs.com/rubylouvre/dom/node.js)的情形 var _u = module.match(reg); url = _u && _u[1] ? _u[1] : this .getBasePath()+ "/" + str + ".js" ; if (! this .requestedUrl[url]){ this .requestedUrl[url] = 1; var script = DOM.createElement( "script" ); script.charset = "utf-8" ; script.defer = true ; // script.async = true;//不能保证多个script标签的执行顺序 script.src = url; //避开IE6的base标签bug //http://www.cnblogs.com/rubylouvre/archive/2010/05/18/1738034.html DOM.documentElement.firstChild.insertBefore(script, null ); // this .removeScript(script); } }, removeScript: function (script){ //移除临时生成的节点 var parent = script.parentNode; if (parent&&parent.nodeType === 1){ script.onload = script.onreadystatechange = function (){ if (-[1,] || this .readyState === "loaded" || this .readyState === "complete" ){ if ( this .clearAttributes) { this .clearAttributes(); } else { this .onload = this .onreadystatechange = null ; } parent.removeChild( this ) } } } } }); //先行取得核心模块的URL dom.require.getBasePath(); window.dom = dom; })( this , this .document); |
示例:
<! doctype html> < html > < head > < meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> < title >并行加载器 by 司徒正美</ title > < script src="dom.js"></ script > < script > dom.require("dom/string,dom/array",function(){ window.console && window.console.log("这是回调函数") dom.query("调用子模块的方法") }); </ script > </ head > < body > < h1 >并行加载器 by 司徒正美</ h1 > < p >请用FF的firebug观察效果</ p > </ body > </ html > |
其他依赖模块的内容:
//string.js dom.declare( "string" , "dom/query" , function (){ window.console && window.console.log( "已加载string模块" ); }); //array.js dom.declare( "array" , "dom/node" , function (){ window.console && window.console.log( "已加载array模块" ) }); //query.js dom.declare( "query" ,[], function (){ window.console && window.console.log( "已加载query模块" ) dom.query = function (str){ window.console && window.console.log(str) } }); //node.js dom.declare( "node" ,[], function (){ window.console && window.console.log( "已加载node模块" ) }); |
文件目录图

加载顺序图

如果对它有兴趣,可在点此下载看一下。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
2010-02-11 为IE的javascript提速
2010-02-11 自动执行函数