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

  1. 定义封装的模块。
  2. 定义新模块对其他模块的依赖。
  3. 可对其他模块的引入支持。

CommonJS
nodeJs出现后使用了CommonJS规范来解决JS的模块化问题。由于Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范也是同步加载依赖模块,加载完后执行后面代码。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。

AMD和CMD

AMD(Asynchromous Module Definition)是equireJS 在推广过程中对模块定义的规范化产出。CMD是SeaJS 在推广过程中对模块定义的规范化产出。RequireJs出自dojo加载器的作者James Burke,SeaJs出自国内前端大师玉伯。二者的区别,玉伯在12年如是说

RequireJS 和 SeaJS 都是很不错的模块加载器,两者区别如下:
两者定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。SeaJS 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 服务器端.
两者遵循的标准有差异。RequireJS 遵循的是 AMD(异步模块定义)规范,SeaJS 遵循的是 CMD (通用模块定义)规范。规范的不同,导致了两者 API 的不同。SeaJS 更简洁优雅,更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
两者社区理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。SeaJS 不强推,而采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
两者代码质量有差异。RequireJS 是没有明显的 bug,SeaJS 是明显没有 bug。
两者对调试等的支持有差异。SeaJS 通过插件,可以实现 Fiddler 中自动映射的功能,还可以实现自动 combo 等功能,非常方便便捷。RequireJS 无这方面的支持。
两者的插件机制有差异。RequireJS 采取的是在源码中预留接口的形式,源码中留有为插件而写的代码。SeaJS 采取的插件机制则与 Node 的方式一致:开放自身,让插件开发者可直接访问或修改,从而非常灵活,可以实现各种类型的插件。.

其中反对这么做的人的观点

我个人感觉requirejs更科学,所有依赖的模块要先执行好。如果A模块依赖B。当执行A中的某个操doSomething()后,再去依赖执行B模块require('B');如果B模块出错了,doSomething的操作如何回滚? 很多语言中的import, include, useing都是先将导入的类或者模块执行好。如果被导入的模块都有问题,有错误,执行当前模块有何意义?

而依赖dependencies是工厂的原材料,在工厂进行生产的时候,是先把原材料一次性都在它自己的工厂里加工好,还是把原材料的工厂搬到当前的factory来什么时候需要,什么时候加工,哪个整体时间效率更高?

首先回答第一个问题。

第一个问题的题设并不完全正确,“依赖”和“执行”的概念比较模糊。编程语言执行通常分为两个阶段,编译(compilation)和运行(runtime)。对于静态语言(比如C/C++)来说,在编译时如果出现错误,那可能之前的编译都视为无效,的确会出现描述中需要回滚或者重新编译的问题。但对于动态语言或者脚本语言,大部分执行都处在运行时阶段或者解释器中:假设我使用Nodejs或者Python写了一段服务器运行脚本,在持续运行了一段时间之后因为某项需求要加载某个(依赖)模块,同时也因为这个模块导致服务端挂了——我认为这时并不存在回滚的问题。在加载依赖模块之前当前的模块的大部分功能已经成功运行了。

再回答第二个问题。

对于“工厂”和“原材料”的比喻不够恰当。难道依赖模块没有加载完毕当前模块就无法工作吗?requirejs的确是这样的,从上面的截图可以看出,依赖模块总是先于当前模块加载和执行完毕。但我们考虑一下基于CommonJS标准的Nodejs的语法,使用require函数加载依赖模块可以在页面的任何位置,可以只是在需要的时候。也就是说当前模块不必在依赖模块加载完毕后才执行。

你可能会问,为什么要拿AMD标准与CommonJS标准比较,而不是CMD标准?

玉伯在CommonJS 是什么这篇文章中已经告诉了我们CMD某种程度上遵循的就是CommonJS标准:

从上面可以看出,Sea.js 的初衷是为了让 CommonJS Modules/1.1 的模块能运行在浏览器端,但由于浏览器和服务器的实质差异,实际上这个梦无法完全达成,也没有必要去达成。

更好的一种方式是,Sea.js 专注于 Web 浏览器端,CommonJS 则专注于服务器端,但两者有共通的部分。对于需要在两端都可以跑的模块,可以 有便捷的方案来快速迁移。

CMD推崇依赖就近,可以把依赖写进你的代码中的任意一行,例:

  1. define(function(require, exports, module) {
  2. var a = require('./a')
  3. a.doSomething()
  4. var b = require('./b')
  5. b.doSomething()
  6. })

代码在运行时,首先是不知道依赖的,需要遍历所有的require关键字,找出后面的依赖。具体做法是将function toString后,用正则匹配出require关键字后面的依赖。显然,这是一种牺牲性能来换取更多开发便利的方法。

而AMD是依赖前置的,换句话说,在解析和执行当前模块之前,模块作者必须指明当前模块所依赖的模块,表现在require函数的调用结构上为:

  1. define(['./a','./b'],function(a,b){
  2. a.doSomething()
  3. b.doSomething()
  4. })

代码在一旦运行到此处,能立即知晓依赖。而无需遍历整个函数体找到它的依赖,因此性能有所提升,缺点就是开发者必须显式得指明依赖——这会使得开发工作量变大,比如:当你写到函数体内部几百上千行的时候,忽然发现需要增加一个依赖,你不得不回到函数顶端来将这个依赖添加进数组。

细心的读者可能发现,到目前位置我讨论的AMD和CMD的思想的关于依赖的部分,都只讨论的“硬依赖”,也就是执行前肯定需要的依赖,但是这不是全部的情况。有的时候情况是这样的:

  1. // 函数体内:
  2. if(status){
  3. a.doSomething()
  4. }

在这个函数体内,可能依赖a,也可能不依赖a,我把这种可能的依赖成为“软依赖”。对于软依赖当然可以直接当硬依赖处理,但是这样不经济,因为依赖是不一定的,有可能加载了此处的依赖而实际上没有用上。
对于软依赖的处理,我推荐依赖前置+回调函数的实现形式。上面的例子简单表述如下:

  1. // 函数体内:
  2. if(status){
  3. async(['a'],function(a){
  4. a.doSomething()
  5. })
  6. }

至此可以对由commonJS衍生出来的方案做出总结了。在浏览器端来设计模块加载机制,需要考虑依赖的问题。
我们先把依赖分为两种,“强依赖” —— 肯定需要 和“弱依赖” —— 可能需要。
对于强依赖,如果要性能优先,则考虑参照依赖前置的思想设计你的模块加载器;如果考虑开发成本优先,则考虑按照依赖就近的思想设计你的模块加载器。
对于弱依赖,只需要将弱依赖的部分改写到回调函数内即可。
无论AMDCMD都要面临以下几个问题:

  1、模块式如何注册的,define函数都做了什么?
  2、他们是如何知道模块的依赖?
  3、如何做到异步加载?尤其是seajs如何做到异步加载延迟执行的?
  辩证法第一规律:事物之间具有有机联系。AMDCMD都借鉴了CommonJs,宏观层面必有一致性,比如整体处理流程:

模块的加载解析到执行过程一共经历了6个步骤:

  1、由入口进入程序

  2、进入程序后首先要做的就是建立一个模块仓库(这是防止重复加载模块的关键),JavaScript原生的object对象最为适合,key代表模块Id,value代表各个模块,处理主模块

  3、向模块仓库注册一模块,一个模块最少包含四个属性:id(唯一标识符)、deps(依赖项的id数组)、factory(模块自身代码)、status(模块的状态:未加载、已加载未执行、已执行等),放到代码中当然还是object最合适

  4、模块即是JavaScript文件,使用无阻塞方式(动态创建script标签)加载模块
5、模块加载完毕后,获取依赖项(amd、cmd区别),改变模块status,由statuschange后,检测所有模块的依赖项。

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

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

处理流程摘自:以代码爱好者角度来看AMDCMD
写法举例
CommonJs

  1. // 文件名: foo.js
  2. define(['jquery', 'underscore'], function ($, _) {
  3. // 方法
  4. function a(){}; // 私有方法,因为没有被返回(见下面)
  5. function b(){}; // 公共方法,因为被返回了
  6. function c(){}; // 公共方法,因为被返回了
  7.      //    暴露公共方法
  8.     return {
  9.         b: b,
  10.         c: c
  11.     }
  12. });
  13. ```
  14. AMD
  15. ```javascript
  16. //    文件名: foo.js
  17. var $ = require('jquery');
  18. var _ = require('underscore');
  19. // methods
  20. function a(){}; // 私有方法,因为它没在module.exports中 (见下面)
  21. function b(){}; // 公共方法,因为它在module.exports中定义了
  22. function c(){}; // 公共方法,因为它在module.exports中定义了
  23. // 暴露公共方法
  24. module.exports = {
  25. b: b,
  26. c: c
  27. };

CMD

  1. define(function (requie, exports, module) {
  2. //依赖可以就近书写
  3. var a = require('./a');
  4. a.test();
  5. ...
  6. //软依赖
  7. if (status) {
  8. var b = requie('./b');
  9. b.test();
  10. }
  11. });

UMD
UMD是AMD和CommonJS的糅合,兼容了AMD和CommonJS,同时还支持老式的“全局”变量规范。
AMD 浏览器第一的原则发展 异步加载模块。CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。
在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

  1. (function (root, factory) {
  2. if (typeof define === 'function' && define.amd) {
  3. // AMD
  4. define(['jquery', 'underscore'], factory);
  5. } else if (typeof exports === 'object') {
  6. // Node, CommonJS之类的
  7. module.exports = factory(require('jquery'), require('underscore'));
  8. } else {
  9. // 浏览器全局变量(root 即 window)
  10. root.returnExports = factory(root.jQuery, root._);
  11. }
  12. }(this, function ($, _) {
  13. // 方法
  14. function a(){}; // 私有方法,因为它没被返回 (见下面)
  15. function b(){}; // 公共方法,因为被返回了
  16. function c(){}; // 公共方法,因为被返回了
  17. // 暴露公共方法
  18. return {
  19. b: b,
  20. c: c
  21. }
  22. }));

AMD、CMD、UMD 模块的写法
关于 CommonJS AMD CMD UMD
扩展阅读:
AMD规范文档
amdjs 的 require 接口文档
amdjs 的接口文档
RequireJS官网接口文档
模块系统
前端模块化开发的价值
前端模块化开发那点历史
CMD 模块定义规范
SeaJS API快速参考
从 CommonJS 到 Sea.js
RequireJS和AMD规范
CommonJS规范
Javascript模块化编程
Javascript模块化编程
知乎 AMD 和 CMD 的区别有哪些?
JavaScript模块化开发 - CommonJS规范
JavaScript模块化开发 - AMD规范
模块化设计
模块化

JS模块化规范CommonJS,AMD,CMD的更多相关文章

  1. (转) 前端模块化:CommonJS,AMD,CMD,ES6

    模块化的开发方式可以提高代码复用率,方便进行代码的管理.通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数.目前流行的js模块化规范有CommonJS.AMD.CMD以及ES6的模块 ...

  2. JavaScript模块化演变 CommonJs,AMD, CMD, UMD(一)

    原文链接:https://www.jianshu.com/p/33d53cce8237 原文系列2链接:https://www.jianshu.com/p/ad427d8879cb 前端完全手册: h ...

  3. js模块化规范—commonjs

    commonjs规范说明 每个js文件都可当作一个模块 在服务器端: 模块的加载是运行时同步加载的(不会阻塞,等待时间回比较长).在浏览器端: 模块需要提前编译打包处理 commonjs规范基本语法 ...

  4. JS模块规范:AMD,CMD,CommonJS

    浅析JS模块规范 随着JS模块化编程的发展,处理模块之间的依赖关系成为了维护的关键. AMD,CMD,CommonJS是目前最常用的三种模块化书写规范. CommonJS CommonJS规范是诞生比 ...

  5. JavaScript模块化CommonJS/AMD/CMD/UMD/ES6Module的区别

    目录 JS-模块化进程 原始的开发方式 CommonJS && node.js AMD && Require.js CMD && Sea.js UMD ...

  6. 前端模块化方案全解(CommonJS/AMD/CMD/ES6)

    模块化的开发方式可以提高代码复用率,方便进行代码的管理.通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数.目前流行的js模块化规范有CommonJS.AMD.CMD以及ES6的模块 ...

  7. CommonJS, AMD, CMD是什么及区别--简单易懂有实例

    CommonJS, AMD, CMD都是JS模块化的规范. CommonJS是服务器端js模块化的规范,NodeJS是这种规范的实现. AMD(异步模块定义)和CMD(通用模块定义)都是浏览器端js模 ...

  8. JS 模块化 - 02 Common JS 模块化规范

    1 Common JS 介绍 Common JS 是模块化规范之一.每个文件都是一个作用域,文件里面定义的变量/函数都是私有的,对其他模块不可见.Common JS 规范在 Node 端和浏览器端有不 ...

  9. js模块化规范AMD、CMD、CommonJS...

    1. AMD 1.1 什么是AMD? AMD 英文名 Asynchronous Module Definition ,中文名 异步模块定义 .这是一个浏览器模块化开发的规范. 由于浏览器环境执行环境的 ...

随机推荐

  1. 【仿携程JQuery日期价格表】

    今天比较闲所以就花点时间又写了点东西. 相信这种价格表大家不会陌生 现在我就模仿它做一个简单版本的.效果如下 首先需要两个时间控件,我这里用的是HTML5里面的时间控件,这个没限制喜欢用什么就用什么 ...

  2. 安装JDK设置环境变量

    PS:之前在CSDN上写的文章,现在转到博客园~ 在安装过程中第一次让选择jdk的安装路径,第二次让选择jre的安装路径.两者不可以在同一个文件夹下,否则在cmd中运行javac时会报:摘不到或无法加 ...

  3. Android线程池的使用(未完)

    ExecutorService Executors public class Executors // 创建一个线程池,使用固定数量的线程操作共享无界队列. public static Executo ...

  4. __sync_fetch_and_add

    最近在公司离职的前辈写的代码哪里看到了__sync_fetch_and_add这个东东.比较好奇.找些资料学习学习 http://www.lxway.com/4091061956.htm http:/ ...

  5. 2014年度辛星css教程夏季版第五节

    本小节我们讲解css中的”盒模型“,即”box model“,它通常用于在布局的时候使用,这个”盒模型“也有人成为”框模型“,其实原理都一样,它的大致原理是这样的,它把一个HTML元素分为了这么几个部 ...

  6. HBase Shell(转)

    HBase 为用户提供了一个非常方便的使用方式, 我们称之为“HBase Shell”.HBase Shell 提供了大多数的 HBase 命令, 通过 HBase Shell 用户可以方便地创建.删 ...

  7. js格式化数字 金额按千位逗号分隔

    // 返回数字 function removeFormatMoney(s) { return parseFloat(s.replace(/[^\d\.-]/g, "")); } / ...

  8. JavaScript跨站脚本攻击

    跨站脚本攻击(Cross-Site Scrpting)简称为XSS,指的是向其他域中的页面的DOM注入一段脚本,该域对其他用户可见.恶意用户可能会试图利用这一弱点记录用户的击键或操作行为,以窃取用户的 ...

  9. UVA 11739 Giving Candies

    求最大的公共前缀: 用后缀数组做: 其实暴力也可以过: #include<cstdio> #include<cstring> #include<algorithm> ...

  10. Jmeter 使用笔记之 html 报告扩展(一)

    题记:在用 loadrunner 的时候可以生成一个 HTML 的报告,并且里面包含各种图表,各种详细的数据.而在使用 Jmeter 测试完后并不能直接生成 Html 的报告(无论是用 GUI 还是命 ...