美文网首页
AMD(Modules/Asynchronous-Definit

AMD(Modules/Asynchronous-Definit

作者: John_Phil | 来源:发表于2019-06-09 23:44 被阅读0次

    一.名词来源

    AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
    https://github.com/amdjs/amdjs-api/wiki/AMD
    CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
    https://github.com/seajs/seajs/issues/242
    https://github.com/cmdjs/specification/blob/master/draft/module.md

    二.名词释义

    1.AMD

    AMD是 Asynchronous Module Definition 的简称,即“异步模块定义”,是从 CommonJS 讨论中诞生的。AMD 优先照顾浏览器的模块加载场景,使用了异步加载和回调的方式。
    The Asynchronous Module Definition (AMD) API specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded. This is particularly well suited for the browser environment where synchronous loading of modules incurs performance, usability, debugging, and cross-domain access problems.
    异步模块定义(AMD)API指定了一种定义模块的机制,以便可以异步加载模块及其依赖项。这特别适用于浏览器环境,其中模块的同步加载会导致性能,可用性,调试和跨域访问问题。

    2.CMD

    CMD是Common Module Definition的简称,即“常见模块定义”解释为
    This specification addresses how modules should be written in order to be interoperable in browser-based environment. By implication, this specification defines the minimum features that a module system must provide in order to support interoperable modules.
    该规范解决了如何编写模块以便在基于浏览器的环境中实现互操作。通过引入,该规范定义了模块系统必须提供的最小特征,以支持可互操作的模块。

    三.代码示范

    AMD

    AMD 的诞生,就是为了解决这两个问题:
    1.实现 js 文件的异步加载,避免网页失去响应
    2.管理模块之间的依赖性,便于代码的编写和维护
    AMD(异步模块定义)主要为前端 JS 的表现指定规范。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
    本规范只定义了一个函数 "define",它是全局变量。函数的描述为:
    define(id?, dependencies?, factory);
    第一个参数,id
    是个字符串。它指的是定义中模块的名字,这个参数是可选的。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。

    模块名的格式

    模块名用来唯一标识定义中模块,它们同样在依赖数组中使用。AMD的模块名规范是CommonJS模块名规范的超集。引用如下:

    • 模块名是由一个或多个单词以正斜杠为分隔符拼接成的字符串
    • 单词须为驼峰形式,或者".",".."
    • 模块名不允许文件扩展名的形式,如".js"
    • 模块名可以为 "相对的" 或 "顶级的"。如果首字符为"."或".."则为"相对的"模块名
    • 顶级的模块名从根命名空间的概念模块解析
    • 相对的模块名从 "require" 书写和调用的模块解析

    上文引用的CommonJS模块id属性常被用于JavaScript模块。

    相对模块名解析示例:

    • 如果模块 "a/b/c" 请求 "../d", 则解析为"a/d"
    • 如果模块 "a/b/c" 请求 "./e", 则解析为"a/b/e"

    如果AMD的实现支持加载器插件(Loader-Plugins),则"!"符号用于分隔加载器插件模块名和插件资源名。由于插件资源名可以非常自由地命名,大多数字符都允许在插件资源名使用。

    第二个参数,dependencies
    是个定义中模块所依赖模块的数组。依赖模块必须根据模块的工厂方法优先级执行,并且执行的结果应该按照依赖数组中的位置顺序以参数的形式传入(定义中模块的)工厂方法中。
    依赖的模块名如果是相对的,应该解析为相对定义中的模块。换句话来说,相对名解析为相对于模块的名字,并非相对于寻找该模块的名字的路径。
    本规范定义了三种特殊的依赖关键字。如果"require","exports", 或 "module"出现在依赖列表中,参数应该按照CommonJS模块规范自由变量去解析。
    依赖参数是可选的,如果忽略此参数,它应该默认为["require", "exports", "module"]。然而,如果工厂方法的形参个数小于3,加载器会选择以函数指定的参数个数调用工厂方法。

    第三个参数,factory
    为模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。
    如果工厂方法返回一个值(对象,函数,或任意强制类型转换为true的值),应该为设置为模块的输出值。
    为了清晰的标识全局函数(为浏览器加载script必须的)遵从AMD编程接口,任何全局函数应该有一个"amd"的属性,它的值为一个对象。这样可以防止与现有的定义了define函数但不遵从AMD编程接口的代码相冲突。
    define.amd
    define.amd对象的属性没有包含在本规范中。实现本规范的作者,可以用它通知超出本规范编程接口基本实现的额外能力。
    define.amd的存在表明函数遵循本规范。如果有另外一个版本的编程接口,那么应该定义另外一个属性,如define.amd2,表明实现只遵循该版本的编程接口。
    一个如何定义同一个环境中允许多次加载同一个版本的模块的实现:

        define.amd = {
          multiversion: true
        };
    

    最简短的定义:

        define.amd = {};
    

    使用 require 和 exports

    创建一个名为"alpha"的模块,使用了require,exports,和名为"beta"的模块:

       define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
           exports.verb = function() {
               return beta.verb();
               //Or:
               return require("beta").verb();
           }
       });
    

    一个返回对象的匿名模块:

       define(["alpha"], function (alpha) {
           return {
             verb: function(){
               return alpha.verb() + 2;
             }
           };
       });
    

    一个没有依赖性的模块可以直接定义对象:

       define({
         add: function(x, y){
           return x + y;
         }
       });
    

    一个使用了简单CommonJS转换的模块定义:

       define(function (require, exports, module) {
         var a = require('a'),
             b = require('b');
    
         exports.action = function () {};
       });
    

    全局变量
    本规范保留全局变量"define"以用来实现本规范。包额外信息异步定义编程接口是为将来的CommonJS API保留的。模块加载器不应在此函数添加额外的方法或属性。
    本规范保留全局变量"require"被模块加载器使用。模块加载器可以在合适的情况下自由地使用该全局变量。它可以使用这个变量或添加任何属性以完成模块加载器的特定功能。它同样也可以选择完全不使用"require"。

    CMD

    在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。该规范明确了模块的基本书写格式和基本交互规则。
    在 CMD 规范中,一个模块就是一个文件。define 是一个全局函数,用来定义模块。

    define(factory);
    

    define 接受 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串。
    factory 为对象、字符串时,表示模块的接口就是该对象、字符串。比如可以如下定义一个 JSON 数据模块:

    define({ "foo": "bar" });
    

    也可以通过字符串定义模板模块:

    define('I am a template. My name is {{name}}.');
    

    factory 为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory 方法在执行时,默认会传入三个参数:require、exports 和 module:

    define(function(require, exports, module) {
    
      // 模块代码
    
    });
    

    define define(id?, deps?, factory)
    define 也可以接受两个以上参数。字符串 id 表示模块标识,数组 deps 是模块依赖。比如:
    (id 和 deps 参数可以省略。省略时,可以通过构建工具自动生成)

    define('hello', ['jquery'], function(require, exports, module) {
    
      // 模块代码
    
    });
    

    define.cmd Object
    一个空对象,可用来判定当前页面是否有 CMD 模块加载器:

    if (typeof define === "function" && define.cmd) {
      // 有 Sea.js 等 CMD 模块加载器存在
    }
    

    require 是 factory 函数的第一个参数。

    require require(id)
    require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口。

    define(function(require, exports) {
    
      // 获取模块 a 的接口
      var a = require('./a');
    
      // 调用模块 a 的方法
      a.doSomething();
    
    });
    

    require 书写约定

    使用 Sea.js 书写模块代码时,需要遵循一些简单规则。
    只是书写和调试时的规范!!!构建后的代码完全不需要遵循下面的约定!!!!!!

    1. 正确拼写
      模块 factory 构造方法的第一个参数 必须 命名为 require 。
    // 错误!
    define(function(req) {
      // ...
    });
    
    // 正确!
    define(function(require) {
      // ...
    });
    
    1. 不要重命名 require 函数,或在任何作用域中给 require 重新赋值。
    // 错误 - 重命名 "require"!
    var req = require, mod = req("./mod");
    
    // 错误 - 重定义 "require"!
    require = function() {};
    
    // 错误 - 重定义 "require" 为函数参数!
    function F(require) {}
    
    // 错误 - 在内嵌作用域内重定义了 "require"!
    function F() {
      var require = function() {};
    }
    
    1. 使用直接量
      require 的参数值 必须 是字符串直接量。
    // 错误!
    require(myModule);
    
    // 错误!
    require("my-" + "module");
    
    // 错误!
    require("MY-MODULE".toLowerCase());
    
    // 正确!
    require("my-module");
    在书写模块代码时,必须遵循这些规则。其实只要把 require 看做是语法关键字 就好啦。
    

    关于动态依赖
    有时会希望可以使用 require 来进行条件加载:

    if (todayIsWeekend)
      require("play");
    else
      require("work");
    

    但请牢记,从静态分析的角度来看,这个模块同时依赖 play 和 work 两个模块,加载器会把这两个模块文件都下载下来。 这种情况下,推荐使用 require.async 来进行条件加载。
    require.async(id, callback?) (cmd实现异步加载,依赖先执行)
    require.async 方法用来在模块内部异步加载模块,并在加载完成后执行指定回调。callback 参数可选。

    define(function(require, exports, module) {
    
      // 异步加载一个模块,在加载完成时,执行回调
      require.async('./b', function(b) {
        b.doSomething();
      });
    
      // 异步加载多个模块,在加载完成时,执行回调
      require.async(['./c', './d'], function(c, d) {
        c.doSomething();
        d.doSomething();
      });
    
    });
    

    注意:require 是同步往下执行,require.async 则是异步回调执行。require.async 一般用来加载可延迟异步加载的模块。

    require.resolve(id)
    使用模块系统内部的路径解析机制来解析并返回模块路径。该函数不会加载模块,只返回解析后的绝对路径。

    define(function(require, exports) {
      console.log(require.resolve('./b'));
      // ==> http://example.com/path/to/b.js
    
    });
    

    这可以用来获取模块路径,一般用在插件环境或需动态拼接模块路径的场景下。
    exports Object
    exports 是一个对象,用来向外提供模块接口。

    define(function(require, exports) {
    
      // 对外提供 foo 属性
      exports.foo = 'bar';
    
      // 对外提供 doSomething 方法
      exports.doSomething = function() {};
    
    });
    

    除了给 exports 对象增加成员,还可以使用 return 直接向外提供接口。

    define(function(require) {
      // 通过 return 直接提供接口
      return {
        foo: 'bar',
        doSomething: function() {}
      };
    });
    

    如果 return 语句是模块中的唯一代码,还可简化为:

    define({
      foo: 'bar',
      doSomething: function() {}
    });
    

    上面这种格式特别适合定义 JSONP 模块。

    特别注意:下面这种写法是错误的!

    define(function(require, exports) {
      // 错误用法!!!
      exports = {
        foo: 'bar',
        doSomething: function() {}
      };
    });
    

    正确的写法是用 return 或者给 module.exports 赋值:

    define(function(require, exports, module) {
    
      // 正确写法
      module.exports = {
        foo: 'bar',
        doSomething: function() {}
      };
    
    });
    

    提示:exports 仅仅是 module.exports 的一个引用。在 factory 内部给 exports 重新赋值时,并不会改变 module.exports 的值。因此给 exports 赋值是无效的,不能用来更改模块接口。

    module Object
    module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。

    module.id String
    模块的唯一标识。

    define('id', [], function(require, exports, module) {
      // 模块代码
    });
    

    上面代码中,define 的第一个参数就是模块标识。

    module.uri String
    根据模块系统的路径解析规则得到的模块绝对路径。

    define(function(require, exports, module) {
      console.log(module.uri); 
      // ==> http://example.com/path/to/this/file.js
    });
    

    一般情况下(没有在 define 中手写 id 参数时),module.id 的值就是 module.uri,两者完全相同。

    module.dependencies Array
    dependencies 是一个数组,表示当前模块的依赖。

    module.exports Object
    当前模块对外提供的接口。

    传给 factory 构造方法的 exports 参数是 module.exports 对象的一个引用。只通过 exports 参数来提供接口,有时无法满足开发者的所有需求。 比如当模块的接口是某个类的实例时,需要通过 module.exports 来实现:

    define(function(require, exports, module) {
    
      // exports 是 module.exports 的一个引用
      console.log(module.exports === exports); // true
    
      // 重新给 module.exports 赋值
      module.exports = new SomeClass();
    
      // exports 不再等于 module.exports
      console.log(module.exports === exports); // false
    
    });
    

    注意:对 module.exports 的赋值需要同步执行,不能放在回调函数里。下面这样是不行的:

    // x.js
    define(function(require, exports, module) {
    
      // 错误用法
      setTimeout(function() {
        module.exports = { a: "hello" };
      }, 0);
    
    });
    

    在 y.js 里有调用到上面的 x.js:

    // y.js
    define(function(require, exports, module) {
    
      var x = require('./x');
    
      // 无法立刻得到模块 x 的属性 a
      console.log(x.a); // undefined
    
    });
    

    四.AMD与CMD区别

    AMD推崇依赖前置(异步加载:依赖先执行 +延迟执行)
    CMD推崇就近依赖(运行到需加载,根据顺序执行)
    (AMD的写法是官方推崇的方式,但是同时也支持CMD的写法)

    //AMD的方式
    define(['./a','./b'],function(a,b){
    a.dosmting();
    //省略1W行
    b.dosmting();
    })
    
    //CMD的方式
    define(function(require,exprots,module){
    var a = require('./a');
    a.dosmting();
    //省略1W行
    var b = require('./b');
    b.dosmting();
    })
    

    异步问题
    AMD在加载模块完成后就会执行改模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行

    CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的

    相关文章

      网友评论

          本文标题:AMD(Modules/Asynchronous-Definit

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