解析豆瓣前端轻量框架Do
2010-11-8
豆瓣使用了jquery框架,但jquery没有提供大型网站所需要的模块化管理,所以豆瓣前端团队就在jquery之前再包裹一层轻量级的框架,用来组织js模块并管理模块之间的依赖关系,按依赖关系自动加载js模块。这个keynote有相关信息。
do的源码压缩后基本都是单字符的变量,读起来相当费劲,整理了一下,给各个变量加上适当的名字,也是理解和学习这个轻量框架的过程。
源码很简单,主要思路就是:通过Do.add()全局存储各个模块的信息,在执行do时把各模块的依赖和执行函数整理成一个有先后顺序的队列,依次加载/执行这个队列。
例:
//uibase模块地址是ui.js
//dialog依赖ui模块
Do.add('uibase', {path: 'ui.js', type: 'js'});
Do.add('dialog', {path: 'dialog.js', type: 'js', requires: ["uibase"]});
Do("dialog", function(){
//使用dialog
});
do里的makeQueue方法会生成这样一个队列:[“jquery.js”, “uibase”, “dialog”, function()]
jquery.js是core_lib里的。接着队列执行器Execute就会依次加载jquery.js ui.js dialog.js,最后执行那个依赖dialog模块的function()
整理后的源码:
(function () {
var doc = document,
pathLoaded = {},
pathLoading = {},
isArray = function (k) {
return k.constructor === Array
},
console = function (k) {
if (window.console && window.console.log) {
window.console.log(k)
}
},
g = {
core_lib: ["jquery.js"],
/* mods结构:
'modName' : {
path: 'http://http://img3.douban.com/js/site/packed_common5.js',
type: 'js',
requires: ['common']
}
*/
mods: {}
},
script = doc.getElementsByTagName("script")[0],
loadResource = function (path, type, charset, onload, param) {
if (!path) {
return
}
if (pathLoaded[path]) {
pathLoading[path] = false;
if (onload) {
onload(path, param)
}
return
}
if (pathLoading[path]) {
setTimeout(function () {
loadResource(path, type, charset, onload, param)
}, 1);
return
}
pathLoading[path] = true;
var dom, type = type || path.toLowerCase().substring(path.lastIndexOf(".") + 1);
if (type === "js") {
dom = doc.createElement("script");
dom.setAttribute("type", "text/javascript");
dom.setAttribute("src", path);
dom.setAttribute("async", true)
} else {
if (type === "css") {
dom = doc.createElement("link");
dom.setAttribute("type", "text/css");
dom.setAttribute("rel", "stylesheet");
dom.setAttribute("href", path);
pathLoaded[path] = true
}
}
if (charset) {
dom.charset = charset
}
if (type === "css") {
script.parentNode.insertBefore(dom, script);
if (onload) {
onload(path, param)
}
return
}
dom.onload = dom.onreadystatechange = function () {
if (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") {
pathLoaded[this.getAttribute("src")] = true;
if (onload) {
onload(this.getAttribute("src"), param)
}
dom.onload = dom.onreadystatechange = null
}
};
script.parentNode.insertBefore(dom, script)
},
//把各模块与库的混合数据变成队列
makeQueue = function (arr) {
if (!arr || !isArray(arr)) {
return
}
var i = 0,
item, ret = [],
mods = g.mods,
queue = [],
added = {},
addRequire = function (name) {
var j = 0,
requireMod, requires;
if (added[name]) {
return queue
}
added[name] = true;
if (mods[name].requires) {
requires = mods[name].requires;
for (; requireMod = requires[j++];) {
if (mods[requireMod]) {
addRequire(requireMod);
queue.push(requireMod)
} else {
queue.push(requireMod)
}
}
return queue
}
return queue
};
for (; item = arr[i++];) {
if (mods[item] && mods[item].requires && mods[item].requires[0]) {
queue = [];
added = {};
ret = ret.concat(addRequire(item))
}
ret.push(item)
}
return ret
},
//队列执行器
Execute = function (queue) {
if (!queue || !isArray(queue)) {
return
}
this.queue = queue;
this.current = null
};
Execute.prototype = {
_interval: 10,
start: function () {
var s = this;
this.current = this.next();
if (!this.current) {
this.end = true;
return
}
this.run()
},
run: function () {
var self = this,
mod, current = this.current;
if (typeof current === "function") {
current();
this.start();
return
} else {
if (typeof current === "string") {
if (g.mods[current]) {
mod = g.mods[current];
loadResource(mod.path, mod.type, mod.charset, function (path) {
self.start()
}, self)
} else {
if (/\.js|\.css/i.test(current)) {
loadResource(current, "", "", function (path, f) {
f.start()
}, self)
} else {
this.start()
}
}
}
}
},
next: function () {
return this.queue.shift()
}
};
this.Do = function () {
var args = Array.prototype.slice.call(arguments, 0),
execute = new Execute(makeQueue(g.core_lib.concat(args)));
console(makeQueue(g.core_lib.concat(args)));
execute.start()
};
this.Do.add = function (name, obj) {
if (!name || !obj || !obj.path) {
return
}
g.mods[name] = obj;
};
//费解,这里为啥要传入g.core_lib,没作用。
Do(g.core_lib)
})();
其实有未压缩的地址:
http://img3.douban.com/js/do2.js