美文网首页Web前端之路让前端飞JavaScript 进阶营
动手实现一个AMD模块加载器(三)

动手实现一个AMD模块加载器(三)

作者: 忽如寄 | 来源:发表于2017-11-21 16:16 被阅读68次

    在上一篇文章中,我们的AMD模块加载器基本已经能够使用了,但是还不够,因为我们没有允许匿名模块,以及没有依赖等情况。实际上在amd的规范中规定的就是define函数的前两个参数是可选的,当没有id(模块名)的时候也就意味着不会有模块依赖于这个模块。很显然,我们的define函数的每个参数的类型是不同的,因此我们需要一些函数来做类型判断,如下:

      function isFun(f) {
        return Object.prototype.toString.call(f).toLowerCase().indexOf('function') > -1;
      }
    
      function isArr(arr) {
        return Array.isArray(arr);
      }
    
      function isStr(str) {
        return typeof str === 'string';
      }
    

    将这些类型判断函数运用在define函数,判断这个模块是否有依赖,是否为匿名模块,这是一个比较简单的工作,修改define函数如下:

      function define(name, deps, callback) {
        if(!isStr(name)) {
          callback = deps;
          deps = name;
          name = null;
        }
        
        if(!isArr(deps)) {
          callback = deps;
          deps = [];
        }
    
        if(moduleMap[name]) {
          name=moduleMap[name]
        } 
        name = replaceName(name);
        deps = deps.map(function(ele, i) {
          return replaceName(ele); 
        });
        
        modMap[name] = modMap[name] || {};
        modMap[name].deps = deps;
        modMap[name].status = 'loaded';
        modMap[name].callback = callback;
        modMap[name].oncomplete = modMap[name].oncomplete || [];
      }
    

    进行一次测试,不过在测试之前,我们需要知道的是,我们将匿名模块的name修改为了null,而后面有一个replaceName方法是做name替换的,这里没有判断name是否为null的情况,因此需要在开头做一次判断,增加如下代码:

      function replaceName(name) {
        if(name===null) {
          return name;
        }
        // ......
      }
    

    测试代码如下:

        loadjs.config({
          baseUrl:'./static',
          paths: {
            app: './app'
          }
        });
    
        loadjs.define('cc',['a'], function(a) {
          console.log(1);
          console.log(a.add(1,2));
        });
    
        loadjs.define('ab', function() {
          console.log('ab');
        });
    
        loadjs.define(function() {
          console.log('unknow');
        });
    
        loadjs.use(['ab','cc'],function() {
          console.log('main');
        });
    

    测试结果如下:


    image

    说明正确。此时我们的一个简单的amd模块加载器就这样写完了,删除console增加注释就可以比较好的使用了,最后整理一下代码如下:

    
    (function(root){
      var modMap = {};
      var moduleMap = {};
      var cfg = {
        baseUrl: location.href.replace(/(\/)[^\/]+$/g, function(s, s1){
          return s1
        }),
        path: {
    
        }
      };
      
      // 完整网址
      var fullPathRegExp = /^[(https?\:\/\/) | (file\:\/\/\/)]/;
      
      // 局对路径
      var absoPathRegExp = /^\//;
      
      // 以./开头的相对路径
      var relaPathRegExp = /^\.\//;
      
      // 以../开头的的相对路径
      var relaPathBackRegExp = /^\.\.\//;
    
    
      function isFun(f) {
        return Object.prototype.toString.call(f).toLowerCase().indexOf('function') > -1;
      }
    
      function isArr(arr) {
        return Array.isArray(arr);
      }
    
      function isStr(str) {
        return typeof str === 'string';
      }
      
      function merge(obj1, obj2) {
        if(obj1 && obj2) {
          for(var key in obj2) {
            obj1[key] = obj2[key]
          }
        }
      }
      
      function outputPath(baseUrl, path) {
        if (relaPathRegExp.test(path)) {
          if(/\.\.\//g.test(path)) {
            var pathArr = baseUrl.split('/');
            var backPath = path.match(/\.\.\//g);
            var joinPath = path.replace(/[(^\./)|(\.\.\/)]+/g, '');
            var num = pathArr.length - backPath.length;
            return pathArr.splice(0, num).join('/').replace(/\/$/g, '') + '/' +joinPath;
          } else {
            return baseUrl.replace(/\/$/g, '') + '/' + path.replace(/[(^\./)]+/g, '');
          }
        } else if (fullPathRegExp.test(path)) {
          return path;
        } else if (absoPathRegExp.test(path)) {
          return baseUrl.replace(/\/$/g, '') + path;
        } else {
          return baseUrl.replace(/\/$/g, '') + '/' + path;
        }
      }
    
      function replaceName(name) {
        if(name===null) {
          return name;
        }
        if(fullPathRegExp.test(name) || absoPathRegExp.test(name) || relaPathRegExp.test(name) || relaPathBackRegExp.test(name)) {
          return outputPath(cfg.baseUrl, name);
        } else {
          var prefix = name.split('/')[0] || name;
          if(cfg.paths[prefix]) {
            if(name.split('/').length === 0) {
             return cfg.paths[prefix];
            } else {;
              var endPath = name.split('/').slice(1).join('/');
              return outputPath(cfg.paths[prefix], endPath);
            }
          } else {
            return outputPath(cfg.baseUrl, name);
          }
        }
      }
    
      function fixUrl(name) {
        return name.split('/')[name.split('/').length-1]
      }
      function config(obj) {
        if(obj){
         if(obj.baseUrl) {
           obj.baseUrl = outputPath(cfg.baseUrl, obj.baseUrl);
         }
         if(obj.paths) {
           var base = obj.baseUrl || cfg.baseUrl;
           for(var key in obj.paths) {
             obj.paths[key] = outputPath(base, obj.paths[key]);
           }
         }
          merge(cfg, obj);
        }
      }
    
    
      
      function use(deps, callback) {
        if(deps.length === 0) {
          callback();
        }
        var depsLength = deps.length;
        var params = [];
        
        for(var i = 0; i < deps.length; i++) {
          moduleMap[fixUrl(deps[i])] = deps[i];
          deps[i] = replaceName(deps[i]);
          (function(j){
            loadMod(deps[j], function(param) {
              depsLength--;
              params[j] = param;
              if(depsLength === 0) {
                callback.apply(null, params);
              }
            })
          })(i)
        }
        
      }
    
      function loadMod(name, callback) {
        /*模块还未定义*/
        if(!modMap[name]) {
          modMap[name] = {
            status: 'loading',
            oncomplete: []
          };
          loadscript(name, function() {
            use(modMap[name].deps, function() {
              execMod(name, callback, Array.prototype.slice.call(arguments, 0));
            })
          });
        } else if(modMap[name].status === 'loading') {
          
          // 模块正在加载
          modMap[name].oncomplete.push(callback);
        } else if (!modMap[name].exports){
          
          //模块还未执行完
          use(modMap[name].deps, function() {
            execMod(name, callback, Array.prototype.slice.call(arguments, 0));
          })
        }else {
          callback(modMap[name].exports);
        }
      }
    
      function execMod(name, callback, params) {
        var exp = modMap[name].callback.apply(null, params);
        modMap[name].exports = exp;
        callback(exp);
        execComplete(name);
      }
    
      function execComplete(name) {
        for(var i = 0; i < modMap[name].oncomplete.length; i++) {
          modMap[name].oncomplete[i](modMap[name].exports);
        }
      }
      
      
      function loadscript(name, callback) {
        var doc = document;
        var node = doc.createElement('script');
        node.charset = 'utf-8';
        node.src = name + '.js';
        
        /*为每个模块添加一个随机id*/
        node.id = 'loadjs-js-' + (Math.random() * 100).toFixed(3);
        doc.body.appendChild(node);
        node.onload = function() {
          callback();
        }
      }
    
      function define(name, deps, callback) {
        /*匿名模块*/
        if(!isStr(name)) {
          callback = deps;
          deps = name;
          name = null;
        }
    
        /*没有依赖*/
        if(!isArr(deps)) {
          callback = deps;
          deps = [];
        }
    
        if(moduleMap[name]) {
          name=moduleMap[name]
        } 
        
        name = replaceName(name);
        
        /*对每个依赖名进行路径替换*/
        deps = deps.map(function(ele, i) {
          return replaceName(ele); 
        });
    
        modMap[name] = modMap[name] || {};
        modMap[name].deps = deps;
        modMap[name].status = 'loaded';
        modMap[name].callback = callback;
        modMap[name].oncomplete = modMap[name].oncomplete || [];
      }
    
      var loadjs = {
        define: define,
        use: use,
        config: config
      };
    
      root.define = define;
      root.loadjs = loadjs;
      root.modMap = modMap;
    })(window);
    

    系列文章:
    动手实现一个AMD模块加载器(一)
    动手实现一个AMD模块加载器(二)
    动手实现一个AMD模块加载器(三)

    相关文章

      网友评论

        本文标题:动手实现一个AMD模块加载器(三)

        本文链接:https://www.haomeiwen.com/subject/ewewvxtx.html