您的位置:首页 > Web前端 > JavaScript

javascript【AMD模块加载器】浅析V3(添加CSS加载功能,重构内部流程)

2013-03-12 01:34 531 查看
由于今天正美大大的回复,然后前篇文章的评论里就出现了好多人站出来指责我抄袭,吓的我小心肝都扑通扑通的跳。

虽然这次的模块加载器是参照了正美大大的来写,但是至少我是一行一行代码自己写出来的。然后一个浏览器一个浏览器测试的,就连流程图都重画了好几次。

虽然大体上跟正美的差不多,但是细节上还是有很多不同的。看到哪些回复我也不想说啥。 抄没抄,会不会。明眼人一眼就能看出来,犯不着解释太多。

废话不多说,下面介绍这一版本的改进。额外增加了一个配置项控制并发的数量。因为浏览器的有效并发数是有限的。所以如果你一次性加载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';
});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: