解析豆瓣前端轻量框架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)
})();
评论

2010年11月8日 11:37

其实有未压缩的地址:
http://img3.douban.com/js/do2.js

2010年11月8日 11:38

@micate 汗 原来有 我搜不到 不过算了 当学习学习~

2010年11月8日 11:47

呵呵最近也想扩展一个jquery的自动加载,yahoo也有类似的

2010年12月11日 1:13

看不懂代码的路过拍苍蝇

2011年3月16日 10:20
2011年6月13日 20:40

发现do2.js 代码在ie7下会报错.
do.js 就不会错.