zoukankan      html  css  js  c++  java
  • 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;
    });
  • 相关阅读:
    使用 MVVMLight 命令绑定
    使用 MVVMLight 绑定数据
    在VS中安装/使用 MVVMLight
    关于 MVVMLight 设计模式系列
    DoBox 下载
    Visual Studio使用技巧,创建自己的代码片段
    List 和 ObservableCollection的区别
    HTTP 错误 404.3 解决
    WPF 跟踪命令和撤销命令(复原)
    WPF 自定义命令 以及 命令的启用与禁用
  • 原文地址:https://www.cnblogs.com/lynxcat/p/2953603.html
Copyright © 2011-2022 走看看