javascript【AMD模块加载器】浅析V2(整合DOM ready)

如果你还不了解AMD模块加载器,可以先看看我的前一篇文章,之后又在其基础上做了一点小修改。  

主要是修改了检测循环依赖的函数,不在遍历对象。

第二就是加入了模块配置方法。以及将lynxcat更名为lynx。

还有就是为模块加入了dom ready机制。

var 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 = 2;

                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;

        }()

关于ready方法,其实很简单。 首先是提供一个方法,把用户的ready的方法存入一个callbacks数组里。 然后在检测到dom ready的时候执行callbacks里面的数组,如果是在dom ready之后传入ready的callback就直接执行。  具体检测dom ready就需要看浏览器的支持了,如果是支持标准w3c规范的浏览器DOMContentLoaded 方法,IE则支持onreadystatechange方法。 还可以利用的一个特性就是 doScroll要求最初的文档被完全加载。否则就会出错。 这样一来我们就可以用setInterval方法来不断的检测doScroll是否出错,如果不出错。那么代表文档已经加载完成了。  总体来说ready方法还是很简单的。

然后说说如何把ready整合到模块中。  首先我想到的是,在require中判断是否已经dom ready如果没有就把当前的require传入到ready方法。 也就是说,这个方法是让页面ready完成后在加载模块。  然后这样有一个坏处就是没有充分利用dom ready的这段时间。  后来看了一下别人的实现,又自己思索了一下。   决定把ready作为一个AMD模块放入到框架中。这样一来就就可以用模块加载的原理来处理了。  如果某个模块依赖ready,就一定要在ready之后才执行它的factory方法。   具体实现的方式是将ready作为一个基准模块写入到modules中,不过它不用被加载。而是在检测dom ready之后就将modules中的ready状态改为2。并检测一下是否所有模块都被加载完毕。这样一来就可以将dom加载的这段时间用来加载模块了。 

然而这样一来就又有一个新的bug在里面,就是如果用户真的想加载一个ready方法的时候就会被误认为是dom ready。 关于这点目前还没想到什么办法,不过用户可以通过写url而不是写ready这样的名字来加载。  在下一版的加载器中我会加入部模块自动加前缀来解决这个问题。还有会加入控制并发数。以及对模块合并的简单说明。

以下是全部源码。

View Code
;(function(win, undefined){

    win = win || window;

    var doc = win.document || document,

        head = doc.head || doc.getElementsByTagName("head")[0],

        hasOwn = Object.prototype.hasOwnProperty,

        slice = Array.prototype.slice,

        configure = {},

        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,

                exports : lynx

            }

        },

        moduleCache : {},          //正在加载的队列缓存

        loadings : [],             //getCurrentScript取不到值的时候用来存储当前script onload的回调函数数组



        /**

         * parse module

         * @param {String} id 模块名

         * @param {String} basePath 基础路径

         * @return {Array} 

         */

        parseModule : function(id, basePath){

            var url, result, ret, dir, paths, i, len, ext, 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];

            ext = modname.slice(modname.lastIndexOf('.'));

            if(ext != '.js'){

                url = url + '.js';

            }else{

                modname = modname.slice(0, modname.lastIndexOf('.'));

            }

            if(modname == ''){

                modname = url;

            }

            return [modname, url];

        },



        /**

         * 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 加载完成之后的回调

     * @param {String} parent 父路径

     */

    win.require = lynx.require = function(ids, callback, parent){

        ids = typeof ids === 'string' ? [ids] : ids;

        var i = 0, len = ids.length, flag = true, uuid = parent || lynx.guid('cb_'), path = parent || basePath,

            modules = lynx.modules, moduleCache = lynx.moduleCache, 

            args = [], deps = {}, id, result;

        for(; i < len; i++){

            id = ids[i];

            if(id == 'ready'){

                result = ['ready','ready'];

            }else{

                result = lynx.parseModule(id, path);

            }



            if(!deps[result[1]]){   //减少检测重复依赖的次数

                if(checkCircularDeps(uuid, result[1])){

                    lynx.error('模块[url:'+ uuid +']与模块[url:'+ result[1] +']循环依赖');

                }

                deps[result[1]] = 'lynx';

            }

            args.push(result[1]);



            if(!modules[result[1]]){

                modules[result[1]] = {

                    name : result[0],

                    url : result[1],

                    state : 0,

                    exports : {}

                }

                flag = false;

                lynx.loadJS(result[1]);

            }else if(modules[result[1]].state != 2){

                flag = false;

            }

        }



        moduleCache[uuid] = {

            uuid : uuid,

            factory : callback,

            args : args,

            deps : deps,

            state : 1

        }



        if(flag){

            fireFactory(uuid);

            return checkLoadReady();

        }

    };



    /**

     * @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();

        if(!id){

            lynx.loadings.push(function(id){

                require(dependencies, factory, id);

            });

        }else{

            require(dependencies, factory, id);

        }

    }



    require.amd = define.amd = lynx.modules;



    /**

     * fire factory

     * @param  {String} uuid

     */

    function fireFactory(uuid){

        var moduleCache = lynx.moduleCache, modules = lynx.modules,

        data = moduleCache[uuid], deps = data.args, result,

        i = 0, len = deps.length, args = [];

        for(; i < len; i++){

            args.push(modules[deps[i]].exports)

        }

        result = data.factory.apply(null, args);

        if(modules[uuid]){

            modules[uuid].state = 2;

            modules[uuid].exports = result;

            delete moduleCache[uuid];

        }else{

            delete lynx.moduleCache;

        }

        return result;

    }



    /**

     * 检测是否全部加载完毕

     */

    function checkLoadReady(){

        var moduleCache = lynx.moduleCache, modules = lynx.modules,

            data, prop, deps, mod;

        loop: for (prop in moduleCache) {

            data = moduleCache[prop];

            deps = data.deps;

            for(mod in deps){

                if(hasOwn.call(modules, mod) && modules[mod].state != 2){

                    continue loop;

                }

            }

            if(data.state != 2){

                fireFactory(prop);

                checkLoadReady();

            }

        }

    }



    /**

     * 检测循环依赖

     * @param  {String} id         

     * @param  {Array} dependencie

     */

    function checkCircularDeps(id, dependencie){

        var moduleCache = lynx.moduleCache, depslist = moduleCache[dependencie] ? moduleCache[dependencie].deps : {};

        if(hasOwn.call(depslist, id) && depslist[id] == 'lynx'){

            return true;

        }

        return false;

    }



    lynx.mix(lynx, {

        /**

         * 加载JS文件

         * @param  {String} url

         */

        loadJS : function(url){

            var node = doc.createElement("script");

            node[node.onreadystatechange ? 'onreadystatechange' : 'onload'] = function(){

                if(!node.onreadystatechange || /loaded|complete/i.test(node.readyState)){

                    var fn = lynx.loadings.pop();

                    fn && fn.call(null, node.src);

                    lynx.modules[node.src] && lynx.modules[node.src].state == 1;

                    node.onload = node.onreadystatechange = node.onerror = null;

                    head.removeChild(node);

                }

            }

            node.onerror = function(){

                lynx.error('模块[url:'+ node.src +']加载失败');

                node.onload = node.onreadystatechange = node.onerror = null;

                head.removeChild(node);

            }

            node.src = url;

            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;

                }

            }    

        },



        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 = 2;

                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));

使用方法

//hello.js文件

define('hello', 'test',function(test){

    return {world : 'hello, world!' + test};

});



//test.js文件

define('test', function(){

    return 'this is test';

});

//主文件

lynxcat.require('ready','hello',function(hello){

    console.log(hello.world);  //hello, world!this is test;

});

 

你可能感兴趣的:(JavaScript)