解析豆瓣前端轻量框架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