• AMD加载器实现笔记(二)


      AMD加载器实现笔记(一)中,我们实现了一个简易的模块加载器。但到目前为止这个加载器还并不能称为AMD加载器,原因很简单,我们还不支持AMD规范中的config配置。这篇文章中我们来添加对config的中baseUrl和packages的支持。API设计如下:

     1 require.config({
     2     baseUrl: "./",
     3     packages: [{
     4         name: "more",
     5         location: "./more"
     6     }, {
     7         name: "mass",
     8         location: "../"
     9     }, {
    10         name: "wab",
    11         location: "../../../"
    12     }]
    13   });

      主要原则是将baseUrl和packages中location的路径转化为绝对路径。核心算法如下:

      翻译成代码则为:

     1 function getRoute(base, target) {
     2         var bts = base.replace(//$/, "").split('/');  //base dir
     3         var tts = target.split('/'); //target parts
     4         while (isDefined(tts[0])) {
     5             if (tts[0] === '.') {
     6                 return bts.join('/') + '/' + tts.slice(1).join('/');
     7             } else if (tts[0] === '..') {
     8                 bts.pop();
     9                 tts.shift();
    10             } else {
    11                 return bts.join('/') + '/' + tts.join('/');
    12             }
    13         }
    14     };

      

      剩下的处理就变得简单起来,首先得到baseUrl的绝对路径,然后根据baseUrl得到各个package中location的绝对路径。代码如下:

    global.require.config = function(config) {
            this.parsedConfig = {};
            if (config.baseUrl) {
                var currentUrl = getCurrentScript();
                var parts = currentUrl.split('/');
                parts.pop();
                var currentDir = parts.join('/');
                this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl);
            }
            var burl = this.parsedConfig.baseUrl;
            // 得到baseUrl后,location相对baseUrl定位
            this.parsedConfig.packages = [];
            if (config.packages) {
                for (var i = 0, len = config.packages.length; i < len; i++) {
                    var pck = config.packages[i];
                    var cp = {
                        name: pck.name,
                        location: getRoute(burl, pck.location)
                    }
                    this.parsedConfig.packages.push(cp);
                }
            }
            
            console.log(this.parsedConfig);
        }

      

      到了这里模块的依赖模块Id就不用再使用绝对路径了,可以按照正常AMD规范中的来了。如:

    define(["aaa",
    "bbb",
    "ccc",
    "fff"],function(a,b,c,f){
        $.log("已加载ddd模块", 7);
        return {
            bbb: b,
            ddd: "ddd",
            length: arguments.length
        }
    })

      那么问题来了,这个时候module仓库的key该变成生么样子呢?继续保持原来的绝对路径形式,还是使用上文中的moduleId(aaa、bbb、ccc、fff)。答案是前者;使用后者的话,如果一个依赖是相对路径,比如:"./utils",可能会有多个模块都依赖这个id,但这些模块未必是需要同一个utils文件。所以我们程序中对require函数需要做一些修改,将deps中的moduleId转化为绝对路径。

     1  // dep为非绝对路径形式,而modules的key仍然需要绝对路径
     2         deps = deps.map(function(dep) {
     3             var rel = "";
     4             if (/^Bodhi/.test(id)) {
     5                 rel = global.require.parsedConfig.baseUrl;
     6             } else {
     7                 var parts = parent.split('/');
     8                 parts.pop();
     9                 rel = parts.join('/');
    10             }
    11             return getModuleUrl(dep, rel);
    12         });

      getModuleUrl函数的处理方式为:

    • 如果dep在某一package中,则将package的location作为参考目录
    • 如果dep像相对路径,则将baseUrl作为参考目录
    • 以上两种除外,则使用baseUrl来拼接路径

      代码如下:

     1 function getModuleUrl(moduleId, relative) {
     2         function getPackage(nm) {
     3             for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {
     4                 var pck = require.parsedConfig.packages[i];
     5                 if (nm === pck.name) {
     6                     return pck;
     7                 }
     8             }
     9             return false;
    10         }
    11         var mts = moduleId.split('/');
    12         var pck = getPackage(mts[0]);
    13         if (pck) {
    14             mts.shift();
    15             return getRoute(pck.location, mts.join('/'));
    16         } else if (mts[0] === '.' || mts[0] === '..') {
    17             return getRoute(relative, moduleId);
    18         } else {
    19             return getRoute(require.parsedConfig.baseUrl, moduleId);
    20         }
    21     }

      

      到目前为止我们加载器已经支持了config中的baseUrl和packages,下篇文章我们让它来支持paths与shim。

      加载器整体代码如下:

    (function(global){
        global.$ = {
            log: function(m) {
                console.log(m);
            }
        };
        global = global || window;
        modules = {};
        loadings = [];
        loadedJs = [];
        //module: id, state, factory, result, deps;
        global.require = function(deps, callback, parent){
            var id = parent || "Bodhi" + Date.now();
            var cn = 0, dn = deps.length;
            var args = [];
            
             // dep为非绝对路径形式,而modules的key仍然需要绝对路径
            deps = deps.map(function(dep) {
                var rel = "";
                if (/^Bodhi/.test(id)) {
                    rel = global.require.parsedConfig.baseUrl;
                } else {
                    var parts = parent.split('/');
                    parts.pop();
                    rel = parts.join('/');
                }
                return getModuleUrl(dep, rel);
            });
            
            var module = {
                id: id,
                deps: deps,
                factory: callback,
                state: 1,
                result: null
            };
            modules[id] = module;
            
            deps.forEach(function(dep) {
                if (modules[dep] && modules[dep].state === 2) {
                    cn++
                    args.push(modules[dep].result);
                } else if (!(modules[dep] && modules[dep].state === 1) && loadedJs.indexOf(dep) === -1) {
                    loadJS(dep);
                    loadedJs.push(dep);
                }
            });
            if (cn === dn) {
                callFactory(module);
            } else {
                loadings.push(id);
                checkDeps();
            }
        };
        
        global.require.config = function(config) {
            this.parsedConfig = {};
            if (config.baseUrl) {
                var currentUrl = getCurrentScript();
                var parts = currentUrl.split('/');
                parts.pop();
                var currentDir = parts.join('/');
                this.parsedConfig.baseUrl = getRoute(currentDir, config.baseUrl);
            }
            var burl = this.parsedConfig.baseUrl;
            // 得到baseUrl后,location相对baseUrl定位
            this.parsedConfig.packages = [];
            if (config.packages) {
                for (var i = 0, len = config.packages.length; i < len; i++) {
                    var pck = config.packages[i];
                    var cp = {
                        name: pck.name,
                        location: getRoute(burl, pck.location)
                    }
                    this.parsedConfig.packages.push(cp);
                }
            }
            
            console.log(this.parsedConfig);
        }
        
        global.define = function(deps, callback) {
            var id = getCurrentScript();
            if (modules[id]) {
                console.error('multiple define module: ' + id);
            }
            
            require(deps, callback, id);
        };
        
        function getRoute(base, target) {
            var bts = base.replace(//$/, "").split('/');  //base dir
            var tts = target.split('/'); //target parts
            while (isDefined(tts[0])) {
                if (tts[0] === '.') {
                    return bts.join('/') + '/' + tts.slice(1).join('/');
                } else if (tts[0] === '..') {
                    bts.pop();
                    tts.shift();
                } else {
                    return bts.join('/') + '/' + tts.join('/');
                }
            }
        };
        
        function isDefined(v) {
            return v !== null && v !== undefined;
        }
        
        function getModuleUrl(moduleId, relative) {
            function getPackage(nm) {
                for (var i = 0, len = require.parsedConfig.packages.length; i < len; i++) {
                    var pck = require.parsedConfig.packages[i];
                    if (nm === pck.name) {
                        return pck;
                    }
                }
                return false;
            }
            var mts = moduleId.split('/');
            var pck = getPackage(mts[0]);
            if (pck) {
                mts.shift();
                return getRoute(pck.location, mts.join('/'));
            } else if (mts[0] === '.' || mts[0] === '..') {
                return getRoute(relative, moduleId);
            } else {
                return getRoute(require.parsedConfig.baseUrl, moduleId);
            }
        }
        
        function loadJS(url) {
            var script = document.createElement('script');
            script.type = "text/javascript";
            //var url = getModuleUrl(mId, rel);
            script.src = url + '.js';
            script.onload = function() {
                var module = modules[url];
                if (module && isReady(module) && loadings.indexOf(url) > -1) {
                    callFactory(module);
                }
                checkDeps();
            };
            var head = document.getElementsByTagName('head')[0];
            head.appendChild(script);
        };
        
        function checkDeps() {
            for (var p in modules) {
                var module = modules[p];
                if (isReady(module) && loadings.indexOf(module.id) > -1) {
                    callFactory(module);
                    checkDeps(); // 如果成功,在执行一次,防止有些模块就差这次模块没有成功
                }
            }
        };
        
        function isReady(m) {
            var deps = m.deps;
            var allReady = deps.every(function(dep) {
                return modules[dep] && isReady(modules[dep]) && modules[dep].state === 2;
            })
            if (deps.length === 0 || allReady) {
                return true;
            }
        };
        
        function callFactory(m) {
            var args = [];
            for (var i = 0, len = m.deps.length; i < len; i++) {
                args.push(modules[m.deps[i]].result);
            }
            m.result = m.factory.apply(window, args);
            m.state = 2;
            
            var idx = loadings.indexOf(m.id);
            if (idx > -1) {
                loadings.splice(idx, 1);
            }
        };
        
        function getCurrentScript(base) {
            // 参考 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)
                 *  //firefox4+ 可以用document.currentScript
                 */
                stack = stack.split(/[@ ]/g).pop(); //取得最后一行,最后一个空格或@之后的部分
                stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/s/, ""); //去掉换行符
                return stack.replace(/(:d+)?:d+$/i, "").replace(/.js$/, ""); //去掉行号与或许存在的出错字符起始位置
            }
            var nodes = (base ? document : head).getElementsByTagName("script"); //只在head标签中寻找
            for (var i = nodes.length, node; node = nodes[--i]; ) {
                if ((base || node.className === moduleClass) && node.readyState === "interactive") {
                    return node.className = node.src;
                }
            }
        };
    })(window)
    View Code

      测试:

    require([
      'bbb',
      'aaa.bbb.ccc',
      'ccc',
      'ddd',
      'fff'
      ], function(aaabbbccc){
        console.log('simple loader');
        console.log(arguments);
      });

      输出结果:

  • 相关阅读:
    C#中yield return用法
    vs生成命令和属性的宏
    开源的库RestSharp轻松消费Restful Service
    【C#】工具类-FTP操作封装类FTPHelper
    常见编码bug
    用Redis存储Tomcat集群的Session(转载)
    session转载
    pv,uv
    cookie读取设置name
    转载cookie理解
  • 原文地址:https://www.cnblogs.com/dojo-lzz/p/5143838.html
Copyright © 2020-2023  润新知