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

发布于 2022-09-25 22:51:51 字数 8003 浏览 176 评论 0

v2-c0469a6a95f2058a19151c4157088014_r

在上一篇文章中,我们的 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');
    });

测试结果如下:

说明正确。此时我们的一个简单的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);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

忆悲凉

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文