javascript【AMD模块加载器】浅析V3(添加CSS加载功能,重构内部流程)
2013-03-12 01:34
531 查看
由于今天正美大大的回复,然后前篇文章的评论里就出现了好多人站出来指责我抄袭,吓的我小心肝都扑通扑通的跳。
虽然这次的模块加载器是参照了正美大大的来写,但是至少我是一行一行代码自己写出来的。然后一个浏览器一个浏览器测试的,就连流程图都重画了好几次。
虽然大体上跟正美的差不多,但是细节上还是有很多不同的。看到哪些回复我也不想说啥。 抄没抄,会不会。明眼人一眼就能看出来,犯不着解释太多。
废话不多说,下面介绍这一版本的改进。额外增加了一个配置项控制并发的数量。因为浏览器的有效并发数是有限的。所以如果你一次性加载10个模块,就有可能阻塞掉其它的资源加载。
现在内部默认最大并发是4个。将原来的moduleCache变量删除,将所有加在信息都整合到modules中,并标记初始加载函数,在所有模块加载结束后删除初始加在函数。
所有css加载,不计入模块加载中。而且加载css也不会在factory的参数中出现。也就是说如果你这样写也没关系。
不过现在还有一个问题就是加载css检测加载完毕的问题。 由于浏览器对link标签的onload事件支持各不一样,加之就算为之做了兼容也是锦衣夜行。 因为根本不需要知道css的加载情况。
主要的改动就这些,还有一些细节性的改动。去掉了deps属性,检测循环依赖的方法改为使用args而不是之前的deps。将之前的loadJS方法改变为loadSource。 将require方法和load拆分开来。
使用文档碎片来将节点批量插入到页面中,尽量减少修改dom树,减少浏览器重排。
这一版中依然使用了正美大大博客中的哪个获得当前被解析的script的url的方法,不知道园子里的各位朋友有没有更好的办法。在最初的时候我是用模块名称来做唯一的,这样就不用获取正在解析的script的url。 但是重名模块就很难解决了。如果大家有更好的解决办法希望能告知一下。大家一起进步。 下面是最新的源码和使用的方法。
View Code
虽然这次的模块加载器是参照了正美大大的来写,但是至少我是一行一行代码自己写出来的。然后一个浏览器一个浏览器测试的,就连流程图都重画了好几次。
虽然大体上跟正美的差不多,但是细节上还是有很多不同的。看到哪些回复我也不想说啥。 抄没抄,会不会。明眼人一眼就能看出来,犯不着解释太多。
废话不多说,下面介绍这一版本的改进。额外增加了一个配置项控制并发的数量。因为浏览器的有效并发数是有限的。所以如果你一次性加载10个模块,就有可能阻塞掉其它的资源加载。
现在内部默认最大并发是4个。将原来的moduleCache变量删除,将所有加在信息都整合到modules中,并标记初始加载函数,在所有模块加载结束后删除初始加在函数。
所有css加载,不计入模块加载中。而且加载css也不会在factory的参数中出现。也就是说如果你这样写也没关系。
require(['hello','test.css','test'], function(hello,test){ console.log(hello,test); });
不过现在还有一个问题就是加载css检测加载完毕的问题。 由于浏览器对link标签的onload事件支持各不一样,加之就算为之做了兼容也是锦衣夜行。 因为根本不需要知道css的加载情况。
主要的改动就这些,还有一些细节性的改动。去掉了deps属性,检测循环依赖的方法改为使用args而不是之前的deps。将之前的loadJS方法改变为loadSource。 将require方法和load拆分开来。
使用文档碎片来将节点批量插入到页面中,尽量减少修改dom树,减少浏览器重排。
这一版中依然使用了正美大大博客中的哪个获得当前被解析的script的url的方法,不知道园子里的各位朋友有没有更好的办法。在最初的时候我是用模块名称来做唯一的,这样就不用获取正在解析的script的url。 但是重名模块就很难解决了。如果大家有更好的解决办法希望能告知一下。大家一起进步。 下面是最新的源码和使用的方法。
View Code
;(function(win, undefined){ win = win || window; var doc = win.document || document, head = doc.head || doc.getElementsByTagName("head")[0], fragment = document.createDocumentFragment(), hasOwn = Object.prototype.hasOwnProperty, slice = Array.prototype.slice, configure = {total : 4}, basePath = (function(nodes){ var node, url; if(!configure.baseUrl){ node = nodes[nodes.length - 1]; url = (node.hasAttribute ? node.src : node.getAttribute("src", 4)).replace(/[?#].*/, ""); }else{ url = configure.baseUrl; } return url.slice(0, url.lastIndexOf('/') + 1); }(doc.getElementsByTagName('script'))), _lynx = win.lynx; /** * 框架入口 */ function lynx(exp, context){ return new lynx.prototype.init(exp, context); } lynx.prototype = { constructor : lynx, /** * 初始化 * @param {All} expr * @param {All} context * @return {Object} * */ init : function(expr, context){ if(typeof expr === 'function'){ require('ready', expr); } //TODO } } lynx.fn = lynx.prototype.init.prototype = lynx.prototype; /** * 继承方法 */ lynx.fn.extend = lynx.extend = function(){ var args = slice.call(arguments), deep = typeof args[args.length - 1] == 'bollean' ? args.pop() : false; if(args.length == 1){ args[1] = args[0]; args[0] = this; args.length = 2; } var target = args[0], i = 1, len = args.length, source, prop; for(; i < len; i++){ source = args[i]; for(prop in source){ if(hasOwn.call(source, prop)){ if(typeof source[prop] == 'object'){ target[prop] = {}; this.extend(target[prop],source[prop]); }else{ if(target[prop] === undefined){ target[prop] = source[prop]; }else{ deep && (target[prop] = source[prop]); } } } } } }; /** * mix * @param {Object} target 目标对象 * @param {Object} source 源对象 * @return {Object} 目标对象 */ lynx.mix = function(target, source){ if( !target || !source ) return; var args = slice.call(arguments), i = 1, override = typeof args[args.length - 1] === "boolean" ? args.pop() : true, prop; while ((source = args[i++])) { for (prop in source) { if (hasOwn.call(source, prop) && (override || !(prop in target))) { target[prop] = source[prop]; } } } return target; }; lynx.mix(lynx, { modules : { //保存加载模块 ready : { state : 1, type : 1, args : [], exports : lynx } }, urls : [], loading : 0, stacks : [], //getCurrentScript取不到值的时候用来存储当前script onload的回调函数数组 /** * get uuid * @param {String} prefix * @return {String} uuid */ guid : function(prefix){ prefix = prefix || ''; return prefix + (+new Date()) + String(Math.random()).slice(-8); }, /** * noop 空白函数 */ noop : function(){ }, /** * error * @param {String} str */ error : function(str){ throw new Error(str); }, /** * @return {Object} lynx */ noConflict : function(deep) { if ( window.lynx === lynx ) { window.lynx = _lynx; } return lynx; } }); //================================ 模块加载 ================================ /** * 模块加载方法 * @param {String|Array} ids 需要加载的模块 * @param {Function} callback 加载完成之后的回调 */ win.require = lynx.require = function(ids, callback){ ids = typeof ids === 'string' ? [ids] : ids; var modules = lynx.modules, urls = lynx.urls, uuid = lynx.guid('cb_'), data; data = parseModules(ids, basePath); modules[uuid] = { name : 'initialize', type : 2, state : 1, args : data.args, factory : callback }; urls = urls.concat(data.urls); lynx.load(urls); }; /** * @param {String} id 模块名 * @param {String|Array} [dependencies] 依赖列表 * @param {Function} factory 工厂方法 */ win.define = function(id, dependencies, factory){ if(typeof dependencies === 'function'){ factory = dependencies; if(typeof id === 'array'){ dependencies = id; }else if(typeof id === 'string'){ dependencies = []; } }else if (typeof id == 'function'){ factory = id; dependencies = []; } id = lynx.getCurrentScript(); dependencies = typeof dependencies === 'string' ? [dependencies] : dependencies; var handle = function(id, dependencies, factory){ var modules = lynx.modules, urls = lynx.urls; modules[id].factory = factory; modules[id].state = 2; if(!dependencies.length){ fireFactory(id); }else{ var data = parseModules(dependencies, id, true); urls = urls.concat(data.urls); lynx.load(urls); } } if(!id){ lynx.stacks.push(function(dependencies, factory){ return function(id){ handle(id, dependencies, factory); id = null; dependencies = null; factory = null; } }(dependencies, factory)); }else{ handle(id, dependencies, factory); } } require.amd = define.amd = lynx.modules; /** * 解析加载模块信息 * @param {Array} list * @param {String} path * @param {boolean} flag * @return {Object} */ function parseModules(list, basePath, flag){ var modules = lynx.modules, urls = [], args = [], uniqurl = {}, id, result; while(id = list.shift()){ if(modules[id]){ args.push(id); continue; } result = parseModule(id, basePath); modules[basePath] && modules[basePath].args.push(result[1]); flag && checkCircularDeps(result[1], basePath) && lynx.error('模块[url:'+ basePath +']与模块[url:'+ result[1] +']循环依赖'); modules[result[1]] = { type : result[2] === 'js' ? 1 : 2, name : result[0], state : 0, exports : {}, args : [], factory : lynx.noop }; (result[2] === 'js') && args.push(result[1]); if(!uniqurl[result[1]]){ uniqurl[result[1]] = true; urls.push(result[1]); } } return { args : args, urls : urls } } /** * parse module * @param {String} id 模块名 * @param {String} basePath 基础路径 * @return {Array} */ function parseModule(id, basePath){ var url, result, ret, dir, paths, i, len, type, modname, protocol = /^(\w+\d?:\/\/[\w\.-]+)(\/(.*))?/; if(result = protocol.exec(id)){ url = id; paths = result[3] ? result[3].split('/') : []; }else{ result = protocol.exec(basePath); url = result[1]; paths = result[3] ? result[3].split('/') : []; modules = id.split('/'); paths.pop(); for(i = 0, len = modules.length; i < len; i++){ dir = modules[i]; if(dir == '..'){ paths.pop(); }else if(dir !== '.'){ paths.push(dir); } } url = url + '/' + paths.join('/'); } modname = paths[paths.length - 1]; type = modname.slice(modname.lastIndexOf('.') + 1); if(type !== 'js' && type !== 'css'){ type = 'js'; url += '.js'; } return [modname, url, type]; } /** * fire factory * @param {String} uuid */ function fireFactory(uuid){ var modules = lynx.modules, data = modules[uuid], deps = data.args, i = 0, len = deps.length, args = []; for(; i < len; i++){ args.push(modules[deps[i]].exports) } data.exports = data.factory.apply(null, args); data.state = 3; delete data.factory; delete data.args; if(data.type == 2 && data.name == 'initialize'){ delete modules[uuid]; } checkLoadReady(); } /** * 检测是否全部加载完毕 */ function checkLoadReady(){ var modules = lynx.modules, flag = true, data, prop, deps, mod, i , len; for (prop in modules) { data = modules[prop]; if(data.type == 1 && data.state != 2){ //如果还没执行到模块的define方法 continue; } deps = data.args; for(i = 0, len = deps.length; mod = deps[i], i < len ; i++){ if(hasOwn.call(modules, mod) && modules[mod].state != 3){ flag = false; break; } } if(data.state != 3 && flag){ fireFactory(prop); } } } /** * 检测循环依赖 * @param {String} id * @param {Array} dependencie */ function checkCircularDeps(id, dependencie){ var modules = lynx.modules, depslist = modules[id] ? modules[id].args : []; return ~depslist.join(' ').indexOf(dependencie); } /** * create * @param {String} type CSS|JS * @param {String} url * @param {Function} callback */ function loadSource(type, url, callback){ var ndoe, modules = lynx.modules; if(type == 'JS'){ var node = doc.createElement("script"); node[node.onreadystatechange ? 'onreadystatechange' : 'onload'] = function(){ if(!node.onreadystatechange || /loaded|complete/i.test(node.readyState)){ callback(); node.onload = node.onreadystatechange = node.onerror = null; var fn = lynx.stacks.pop(); fn && fn.call(null, node.src); head.removeChild(node); } } node.src = url; modules[url].state = 1; lynx.loading++; }else if(type == 'CSS'){ var node = doc.createElement("link"); node.rel = 'stylesheet'; node.href = url; delete modules[url]; } node.onerror = function(){ lynx.error('模块[url:'+ node.src +']加载失败'); node.onload = node.onreadystatechange = node.onerror = null; lynx.loading--; head.removeChild(node); } return node; }; lynx.mix(lynx, { load : function(urls){ var loading , total = configure.total,modules = lynx.modules, url, node = fragment, type; while((loading = lynx.loading) < total && (url = urls.shift())){ type = url.slice(url.lastIndexOf('.') + 1).toUpperCase(); node.appendChild(loadSource(type, url, function(){ lynx.loading--; var urls = lynx.urls; urls.length && lynx.load(urls); })); } head.insertBefore(node, head.firstChild); }, /** * 加载JS文件 * @param {String} url * @param {Function} callback */ loadJS : function(url, callback){ var node = loadSource('JS', url, callback) head.insertBefore(node, head.firstChild); }, /** * 加载CSS文件 * @param {String} url * @param {Function} callback */ loadCSS : function(url, callback){ var node = loadSource('CSS', url, callback); head.insertBefore(node, head.firstChild); }, /** * get current script [此方法来自司徒正美的博客] * @return {String} */ getCurrentScript : function(){ //取得正在解析的script节点 if (doc.currentScript) { //firefox 4+ return doc.currentScript.src; } // 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js var stack; try { a.b.c(); //强制报错,以便捕获e.stack } catch (e) { //safari的错误对象只有line,sourceId,sourceURL stack = e.stack; if (!stack && window.opera) { //opera 9没有e.stack,但有e.Backtrace,但不能直接取得,需要对e对象转字符串进行抽取 stack = (String(e).match(/of linked script \S+/g) || []).join(" "); } } if (stack) { /**e.stack最后一行在所有支持的浏览器大致如下: *chrome23: * at http://113.93.50.63/data.js:4:1 *firefox17: *@http://113.93.50.63/query.js:4 *opera12:http://www.oldapps.com/opera.php?system=Windows_XP *@http://113.93.50.63/data.js:4 *IE10: * at Global code (http://113.93.50.63/data.js:4:1) */ stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@之后的部分 stack = stack[0] === "(" ? stack.slice(1, -1) : stack; return stack.replace(/(:\d+)?:\d+$/i, ""); //去掉行号与或许存在的出错字符起始位置 } var nodes = head.getElementsByTagName("script"); //只在head标签中寻找 for (var i = 0, node; node = nodes[i++]; ) { if (node.readyState === "interactive") { return node.src; } } }, /** * 配置模块信息 * @param {Object} option */ config : function(option){ lynx.mix(configure, option); }, //============================== DOM Ready ============================= /** * dom ready * @param {Function} callback */ ready : function (){ var isReady = false; var readyList = []; var ready = function(fn){ if(isReady){ fn(); }else{ readyList.push(fn); } }; var fireReady = function(){ for(var i = 0,len = readyList.length; i < len; i++){ readyList[i](); } readyList = []; lynx.modules.ready.state = 3; checkLoadReady(); }; var bindReady = function(){ if(isReady){ return; } isReady=true; fireReady(); if(doc.removeEventListener){ doc.removeEventListener("DOMContentLoaded",bindReady,false); }else if(doc.attachEvent){ doc.detachEvent("onreadystatechange", bindReady); } }; if( doc.readyState === "complete" ) { bindReady(); }else if(doc.addEventListener){ doc.addEventListener("DOMContentLoaded", bindReady, false); }else if(doc.attachEvent){ doc.attachEvent("onreadystatechange", function(){ if((/loaded|complete/).test(doc.readyState)){ bindReady(); } }); (function(){ if(isReady){ return; } var node = new Image(); var timer = setInterval(function(){ try{ isReady || node.doScroll('left'); node = null; }catch(e){ return; } clearInterval(timer); bindReady(); }, 16); }()); } return ready; }() }); win.lynx = lynx; }(window));
//使用方法 //index.html 页面 <html> <head> <title>测试</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script type="text/javascript" src="lynx.js"></script> <script type="text/javascript"> lynx.require(['test.css','hello'], function(hello){ alert(hello); }) </script> </head> <body> <div class="test"></div> </body> </html> //test.css 文件 .test{ width: 200px; height: 100px; background-color: red; } //hello.js 文件 define('hello', 'test',function(test){ return 'a' + test; }); //test.js 文件 define('test',function(test){ return 'b'; });
相关文章推荐
- javascript【AMD模块加载器】浅析
- javascript【AMD模块加载器】浅析V2(整合DOM ready)
- Visual Studio添加对Class内部的if, for循环等块区域的outline功能以及对html,css添加#region模块
- JavaScript AMD 模块加载器原理与实现
- JavaScript异步模块加载机制(AMD)
- 用JavaScript动态加载CSS和JS文件
- mass Framework css模块 v3
- AutoEE_V3-自动智能快速开发平台-模块代码生成功能讲解
- webpack模块加载css文件及图片地址
- 用javascript动态加载不同css/js文件
- javascript+css好多网站用的选星星实现打分功能的函数
- 关于javascript模块加载技术的一些思考
- apache中添加FastCGI模块功能
- Javascript配合CSS实现网页自助换肤功能
- Conditioniz – 探测浏览器并条件加载 JavaScript 和 CSS
- 利用CSS、JavaScript及Ajax实现图片预加载的三大方法
- CSS和JavaScript以及Ajax实现预加载图片的方法及优缺点分析
- 利用CSS、JavaScript及Ajax实现图片预加载的三大方法
- 利用CSS、JavaScript及Ajax实现图片预加载的三大方法
- 纯javascript写的table加载,包含分页、数据下载功能