随着浏览器功能越来越完善,前端已经不仅仅是切图做网站,前端在某些方面已经媲美桌面应用。越来越庞大的前端项目,越来越复杂的代码,前端开发者们对于模块化的需求空前强烈。后来node出现了,跟随node出现的还有commonjs,这是一种js模块化解决方案,像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。但是浏览器环境不同于Node,浏览器中获取一个资源必须要发送http请求,从服务器端获取,采用同步模式必然会阻塞浏览器进程出现假死现象。在这方面dojo曾经做了伟大尝试,早期dojo便是采用xhr+eval的方式,结果可想而知,阻塞现象是必然的。后来出现无阻塞加载脚本方式在开发中广泛应用,在此基础结合commonjs规范,前端模块化迎来了两种方案:AMD、CMD.

  借用三藏法师一句话:人是人他妈生的,妖是妖他妈生的。此话虽不雅,但用这里却颇为贴切。AMD 是 RequireJS 在推广过程中对模块定义的规范化产出,CMD是SeaJS 在推广过程中被广泛认知。RequireJs出自dojo加载器的作者James Burke,SeaJs出自国内前端大师玉伯。二者的区别,玉伯在12年如是说

RequireJS 和 SeaJS 都是很不错的模块加载器,两者区别如下:

1. 两者定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。SeaJS 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 服务器端

2. 两者遵循的标准有差异。RequireJS 遵循的是 AMD(异步模块定义)规范,SeaJS 遵循的是 CMD (通用模块定义)规范。规范的不同,导致了两者 API 的不同。SeaJS 更简洁优雅,更贴近 CommonJS Modules/1.1 和 Node Modules 规范。

3. 两者社区理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。SeaJS 不强推,而采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。

4. 两者代码质量有差异。RequireJS 是没有明显的 bug,SeaJS 是明显没有 bug。

5. 两者对调试等的支持有差异。SeaJS 通过插件,可以实现 Fiddler 中自动映射的功能,还可以实现自动 combo 等功能,非常方便便捷。RequireJS 无这方面的支持。

6. 两者的插件机制有差异。RequireJS 采取的是在源码中预留接口的形式,源码中留有为插件而写的代码。SeaJS 采取的插件机制则与 Node 的方式一致:开放自身,让插件开发者可直接访问或修改,从而非常灵活,可以实现各种类型的插件。.

  关于二者的区别,前人之述备矣:

  而在本文,我们仅从代码爱好者的角度来一窥二者API、模块管理、加载、执行的异同。

  对比AMD与CMD规范,二者最大的区别在于依赖模块的执行时期,CMD规范中明确要求延迟执行(Execution must be lazy.)。这一点从二者在模块的定义方法define的函数签名上可以看出:

  AMD中define如下定义:

   define(id?, dependencies?, factory);
  • id:String类型,它指定了模块被定义时的id;可选的,如果省略,模块id默认使用加载器请求的响应脚本的模块id。
  • dependencies是一个模块定义时要求的依赖项的模块id数组字面量。这些依赖项必须在factory方法执行前被解析,解析值应当被当做参数传递给factory函数;factory的参数位置符合模块在依赖项中的索引。
  • factory,是一个被用来执行模块初始化的参数或者是一个对象。如果factory是一个函数,它应当只能被用来执行一次。如果factory参数是一个对象,这个对象呗用来作为模块的输出值。如果factory函数返回一个值(对象、函数、任何可以被强制转换为true的值),这个值将会被作为模块的输出值。
define(["./a", "./b"], function(a, b) {
//BEGIN
a.doSomething();
b.doSomething();
});

  CMD中模块如下定义:

define(function(require, exports, module) {

  // The module code goes here

});
  一个模块使用define函数来定义
  1. define函数只接受一个模块工厂作为参数
  2. factory必须是一个函数或者其他有效值
  3. 如果factory是一个函数,如果指定参数的话,前三个必须是“require”,“exports”,“module”
  4. 如果factory不是一个函数,那么模块的exports属性被设置为那个有效对象
define(function(require, exports, module) {
//BEGIN
require("./a").doSomething();
require("./b").doSomething();
});

  需要提一下的是二者对待依赖模块的加载是一致的,在factory执行时,依赖模块都已被加载。从代码上来看,AMD中在BEGIN处a、b的factory都是执行过的;而CMD中虽然a、b模块在BEGIN已被加载,但尚未执行,需要调用require执行依赖模块。这就是CMD中着重强调的延迟执行。如果这个例子不明显的话,我们来看一下条件依赖:

  AMD:

define(["./a", "./b"], function(a, b) {
//BEGIN
if (true) {
a.doSomething();
} else {
b.doSomething();
}
//END
});

  CMD:

define(function(require) {
// BEGIN
if(some_condition) {
require('./a').doSomething();
} else {
require('./b').soSomething();
}
// END
});

  条件依赖意思是我们根据条件使用依赖项,在AMD中BEGIN位置处a、b模块都需要被执行一次。CMD中BEGIN处a、b都没有被执行,在END处,a、b只有一个被实际执行过。

  那么问题来了,javascript作为脚本语言,代码肯定是顺序执行的,作为AMD与CMD的实现者,requireJs与seaJs是如何知道需要加载的所有文件呢?又是如何做到异步加载?对于seajs,factory中代码肯定是顺序执行的,但是这必须导致require时的阻塞加载,而她又是如何保证异步加载的?

  每一个卓越的思想都有一份朴实的代码实现。所以无论AMD与CMD都要面临以下几个问题:

  1、模块式如何注册的,define函数都做了什么?
  2、他们是如何知道模块的依赖?
  3、如何做到异步加载?尤其是seajs如何做到异步加载延迟执行的?
  辩证法第一规律:事物之间具有有机联系。AMD与CMD都借鉴了CommonJs,宏观层面必有一致性,比如整体处理流程:
 
 
  模块的加载解析到执行过程一共经历了6个步骤:
  1、由入口进入程序
  2、进入程序后首先要做的就是建立一个模块仓库(这是防止重复加载模块的关键),JavaScript原生的object对象最为适合,key代表模块Id,value代表各个模块,处理主模块
  3、向模块仓库注册一模块,一个模块最少包含四个属性:id(唯一标识符)、deps(依赖项的id数组)、factory(模块自身代码)、status(模块的状态:未加载、已加载未执行、已执行等),放到代码中当然还是object最合适
  4、模块即是JavaScript文件,使用无阻塞方式(动态创建script标签)加载模块

scriptElement= document.createElement('script');
scriptElement.src = moduleUrl;
scriptElement.async = true;
scriptElement.onload = function(){.........};
document.head.appendChild(scriptElement);

  5、模块加载完毕后,获取依赖项(amd、cmd区别),改变模块status,由statuschange后,检测所有模块的依赖项。

  由于requirejs与seajs遵循规范不同,requirejs在define函数中可以很容易获得当前模块依赖项。而seajs中不需要依赖声明,所以必须做一些特殊处理才能否获得依赖项。方法将factory作toString处理,然后用正则匹配出其中的依赖项,比如出现require(./a),则检测到需要依赖a模块。

  同时满足非阻塞和顺序执行就需要需要对代码进行一些预处理,这是由于CMD规范和浏览器环境特点所决定的。

  6、如果模块的依赖项完全加载完毕(amd中需要执行完毕,cmd中只需要文件加载完毕,注意这时候的factory尚未执行,当使用require请求该模块时,factory才会执行,所以在性能上seajs逊于requirejs),执行主模块的factory函数;否则进入步骤3.

  最后,无论requireJs还是seaJs都已被广泛应用于web开发中,实际选取时应根据以下几方面综合平衡选取:

  1、功能能否满足项目需求
  2、文档、demo的详尽程度
  3、框架的学习曲线
  4、社区的活跃度

以代码爱好者角度来看AMD与CMD(转)的更多相关文章

  1. 以代码爱好者角度来看AMD与CMD

    随着浏览器功能越来越完善,前端已经不仅仅是切图做网站,前端在某些方面已经媲美桌面应用.越来越庞大的前端项目,越来越复杂的代码,前端开发者们对于模块化的需求空前强烈.后来node出现了,跟随node出现 ...

  2. JS模块化规范CommonJS,AMD,CMD

    模块化是软件系统的属性,这个系统被分解为一组高内聚,低耦合的模块.理想状态下我们只需要完成自己部分的核心业务逻辑代码,其他方面的依赖可以通过直接加载被人已经写好模块进行使用即可.一个模块化系统所必须的 ...

  3. amd,cmd规范

    AMD 和 CMD 的区别有哪些? AMD规范与CMD规范的区别 回顾:前端模块化和AMD.CMD规范(全) 浅析JS模块规范:AMD,CMD,CommonJS 理解AMD ,CMD,CommonJS ...

  4. 【整理】 JavaScript模块化规范AMD 和 CMD 的区别有哪些?

    根据玉伯等人在知乎上的回答整理.整理中... AMD 规范在这里:https://github.com/amdjs/amdjs-api/wiki/AMD CMD 规范在这里:https://githu ...

  5. 再唠叨JS模块化加载之CommonJS、AMD、CMD、ES6

    Javascript模块化编程,已经成为一个迫切的需求.理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块. Javascript社区做了很多努力,在现有的运行环境中,实现” ...

  6. AMD 和 CMD 的区别有哪些

    在说AMD 和 CMD 的区别之前,先说明commonjs,它的回调和amd.cmd的不同于:commomjs加载完了所有模块,才执行回调amd和cmd是加载对应的模块,就可以执行回调中对应的代码 1 ...

  7. CommonJS、AMD、CMD、NodeJs、RequireJS到底有什么联系?

    JS中的模块规范(CommonJS,AMD,CMD),如果你听过js模块化这个东西,那么你就应该听过或CommonJS或AMD甚至是CMD这些规范,本文包括这三个规范的来源及对应的产物的原理. 一.C ...

  8. CommonJS、AMD与CMD

    自从有了模块,我们可以更方便地使用别人的代码,想要什么功能,就加载什么模块.但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套! 于是下面三个模块 ...

  9. js模块化AMD、CMD、ES6

    AMD CMD ES6模块化 各个模块化规范对比理解 一.AMD 在上一篇js模块化入门与commonjs解析与应用中详细的解析了关于commonjs模块化规范,commonjs采用的用同步加载方式, ...

随机推荐

  1. LOJ#6463 AK YOI 树分治+线段树合并

    传送门 既然是树上路径统计问题,不难想到要使用树分治,这里以点分治为例 由点分治的性质,每层只需要考虑经过重心的路径 因为需要维护路径长度在一定范围内的最大权值和,所以要用一个数据结构维护一下到根节点 ...

  2. 关于HSTS的总结

    访问http网站,和服务器交互的步骤浏览器向服务器发起一次HTTP请求服务器返回一个重定向地址浏览器在发送一次HTTPS请求,得到最终内容 上面浏览器发送http请求后容易被拦截,使用HSTS后可以避 ...

  3. gotop(返回顶部)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. Pwn Heap With Tcache

    Pwn Heap With Tcache 前言 glibc 2.26 开始引入了 tcache , 相关的 commit 可以看 这里 .加入 tcache 对性能有比较大的提升,不过由于 tcach ...

  5. 浅谈maven中的scope,systempath

    scope  maven中scope的默认值是compile scope的分类 1)compile 默认是compile.compile表示被依赖项目需要参与当前项目的编译,包括后续的测试,运行周期也 ...

  6. 2017年秋季个人阅读计划 ---《掌握需求过程》第二版 pdf

    这学期我们学习是软件需求分析,为了扩展视野,我们老师要求精读一本书,我根据老师推荐的书籍中找到了一本,名字叫做<掌握需求过程>,我大概浏览了一下这本书,这本书论述了软件开发中的重要课题—如 ...

  7. 浅谈Java——泛型DAO

    首先解释一下为什么要学习泛型DAO.平时在写DAO的时候是一个接口对应一个实现类,实现类里面要写很多的操作数据库的方法.当我们有很多的javaben的时候我们会写很多的接口和实现类,并且里面的代码都是 ...

  8. viedo formats vs file formats

    web的视频世界,有两个概念非常容易搞混淆,即:视频文件的格式,比如.mp4,.flv,.ogv等等,以及视频本身的格式,就是指的codec算法名称,比如h.264,mpeg-4等. http://w ...

  9. 关于Entity Framework更新的几种方式以及可能遇到的问题(附加类型“Model”的实体失败,因为相同类型的其他实体已具有相同的主键值)在使用 "Attach" 方法或者将实体的状态设置为 "Unchanged" 或 "Modified" 时如果图形中的任何实体具有冲突键值,则可能会发生上述行为

    在日常使用Entity Framework中,数据更新通常会用到.下面就简单封装了一个DBContext类 public partial class EFContext<T> : DbCo ...

  10. linux rz上传文件及出错解决方案

    在把Windows上的文件传至Linux端时用到SecureCRT,一般小文件都没有问题,文件太大时则出现了上传后的文件只有几K大小,当然大于2个G的是不可能传的上去的了.对于几百M到1G多的大文件要 ...