1、简介

随着前端业务复杂度的增加,模块化成为一个大的趋势。而在ES6还未被浏览器所支持的情况下,commonjs作为ES6中标准模块加载方案,在客服端中的支持情况并不好,现在在客服端中有2中模块化的解决方案,CMD和AMD,他们的代表分别为seajs和requirejs。这篇文章主要介绍我对commonjs、AMD以及CMD的理解。

2、commonJS

commonjs的目标是制定一个js模块化的标准,它的目标制定一个可以同时在客服端和服务端运行的模块。这些模块拥有自己独立的作用域,也可以向顶层曝露出自己的api也就是module.exports。在ES6中common被制定为标准。但是在ES6还未被浏览器完美支持的情况下,commonjs规范之能在服务端发挥它的作用。比如在nodejs和webpack等中。而我们服务端的异步加载模块主要有2种加载方案,CMD和AMD,这两种规范的典型是seajs和rjs(requirejs)。这两种方案虽然都是加载模块的解决方案,但是还是有一些的差别。

我们先来了解下commonjs规范

此规范指出了如何编写可以在同类模块系统中所共用的模块,这类模块系统可以同时在客户端和服务端,以安全的或者不安全的方式已经被实现了或者通过语法扩展可以被未来的系统所支持。这些模块需要提供顶级作用域的私有性,并提供从其他模块导入单例对象到自身并且可以导出自身API的能力。含蓄的说,这个规范定义了如果一个模块系统要支持共用模块,那么它需要提供的最少的功能特性。

模块上下文

  1. 在一个模块中,存在一个自由的变量"require",它是一个函数。
    1. 这个"require"函数接收一个模块标识符。
    2. "require"返回外部模块所输出的API。
    3. 如果出现依赖闭环(dependency cycle),那么外部模块在被它的传递依赖(transitive dependencies)所require的时候可能并没有执行完成;在这种情况下,"require"返回的对象必须至少包含此外部模块在调用require函数(会进入当前模块执行环境)之前就已经准备完毕的输出。
    4. 如果请求的模块不能返回,那么"require"必须抛出一个错误。
  2. 在一个模块中,会存在一个名为"exports"的自由变量,它是一个对象,模块可以在执行的时候把自身的API加入到其中。
  3. 模块必须使用"exports"对象来做为输出的唯一表示。

如果不理解请移步CommonJS Modules/1.0 规范 & AMD 规范中文版 这里面会有具体的例子。

从模块的上下文可以可以看出,这只是制定了一种模块的方式,在模块内部用require函数去获取我们所需要的API,用exports抛出当前模块的API。这其实也就是我们node的模块方式,没错,node就是commonjs的实践者,commonjs只是一种规范,而nodejs就是实践这个规范。同样的,ES6中,也执行了commonjs规范,但是介于我们浏览器还不能完美的支持ES6,所以我们也不能用这种很cool的方式。(注解:当然babel是一个完美的工具,把ES6转成目前支持的es5。但是最终在浏览器中执行的代码还是es5。所以我们直接编写的ES6代码,只能通过webpack等打包工具,把模块之间的依赖通过打包工具来实现。)

既然无法在浏览器中使用ES6,那么我们如和在浏览器中去执行模块依赖加载的呢?这里有2中方案去实现浏览器下模块和依赖加载,CMD和AMD,这两种方案的典型是seajs和requirejs。下面我们先分析AMD

3、AMD规范

AMD是为了弥补commonjs规范在浏览器中目前无法支持ES6的一种解决方案。异步模块定义规范(AMD)制定了定义模块的规则,这样模块和模块的依赖可以被异步加载。这和浏览器的异步加载模块的环境刚好适应(浏览器同步加载模块会导致性能、可用性、调试和跨域访问等问题)。

这个规范只定义define函数。

define(id?,dependencies?,factory);

其中id和dependencies不是必须的。

模块的格式:

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

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

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

相对模块名解析示例:

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

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

define.amd (Object)用来标识有amd模块加载器的存在

例子:  

// 创建一个名为"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 () {};
});

4、AMD规范的实践者requirejs

requirejs是AMD规范的实践者,RequireJS 是一个JavaScript模块加载器。它非常适合在浏览器中使用,但它也可以用在其他脚本环境, 就像 Rhino and Node. 使用RequireJS加载模块化脚本将提高代码的加载速度和质量。

在使用requirejs时,可以查看官方文档

requirejs中文文档

requirejs英文文档

requirejs非常简单,我们只需要定义在页面加载的时候,引入requirejs并且,把mainjs指定在data-main中,在mainjs中引入我们的requirejs.config和我们需要用到的页面js,requirejs会根据我们的模块去加载相应的依赖,然后执行代码。

// 页面引入
<script data-main="main" src="./amdjs/require.js"></script> // 模块,这里使用AMD定义模块的方式,例如,定义一个模块module1
define('module1', ['zepto'], function($) {
console.log('this is module1')
}) //mainjs内容
require.config({
baseUrl: 'amdjs/modules',
paths: {
main: 'amdjs/main'
zepto: 'http://zeptojs.com/zepto.min'
},
shim: { },
waitSeconds: 15
});
// 你的模块
requirejs(['module1'],function($) {
console.log('load success!')
})

config文件里面有许多参数,这里我把常用的解释下,具体的请查看requirejs文档。

baseUrl:所有模块的查找根路径。

paths :path映射那些不直接放置于baseUrl下的模块名。设置path时起始位置是相对于baseUrl的,除非该path设置以"/"开头或含有URL协议(如http:)。

shim: 为那些没有使用define()来声明依赖关系、设置模块的"浏览器全局变量注入"型脚本做依赖和导出配置。shim配置仅设置了代码的依赖关系,想要实际加载shim指定的或涉及的模块,仍然需要一个常规的require/define调用。设置shim本身不会触发代码的加载。

deps: 指定要加载的一个依赖数组。当将require设置为一个config object在加载require.js之前使用时很有用。一旦require.js被定义,这些依赖就已加载。使用deps就像调用require([]),但它在loader处理配置完毕之后就立即生效。它并不阻塞其他的require()调用,它仅是指定某些模块作为config块的一部分而异步加载的手段而已。

5、CMD规范

cmd是commonjs另外的一种模块加载方案,这个规范本身偏向于commonjs的规范。他以一个文件就是一个模块和ES6中标准的commonjs规范类似。

它定义了以个define(factory)函数。define 接受 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串。

如果factory为函数时,它有三个参数:require,exports,module。

define.cmd是一个cmd模块加载器的标识

require方法:同步加载模块

define(function(require, exports) {

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

require.async:用来在模块的内部异步加载模块,并且完成后执行指定回掉。

define(function(require, exports, module) {

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

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

define(function(require, exports) {

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

exports:是一个对象,用来向外提供模块接口。

define(function(require) {

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

module.exports:模块暴露的出口

define(function(require, exports, module) {

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

6、CMD规范的实践者seaJS

seajs和requirejs的加载方式类似,在页面引入seajs文件后,加载seajs.config,并且之后加载mainjs。详细信息请查看

seajs文档

seajsAPI

加载seajs

// 引入seajs
<script src="../sea-modules/seajs/seajs/2.2.0/sea.js"></script> // 配置文件
<script>
// Set configuration
seajs.config({
base: "../sea-modules/",
alias: {
"jquery": "jquery/jquery/1.10.1/jquery.js"
}
});
// 入口
// For development
if (location.href.indexOf("?dev") > 0) {
seajs.use("../static/hello/src/main");
}
// For production
else {
seajs.use("examples/hello/1.0.0/main");
}
</script>

API:

seajs.config: 配置信息

seajs.config({

  // 设置路径,方便跨目录调用
paths: {
'arale': 'https://a.alipayobjects.com/arale',
'jquery': 'https://a.alipayobjects.com/jquery'
}, // 设置别名,方便调用
alias: {
'class': 'arale/class/1.0.0/class',
'jquery': 'jquery/jquery/1.10.1/jquery'
} });

更多的详细配置选项请参考页面:seajs config详细

seajs.use:页面加载一个或者多个模块

// 加载一个模块
seajs.use('./a'); // 加载一个模块,在加载完成时,执行回调
seajs.use('./a', function(a) {
a.doSomething();
}); // 加载多个模块,在加载完成时,执行回调
seajs.use(['./a', './b'], function(a, b) {
a.doSomething();
b.doSomething();
});

这里值得一提的是seajs.use的用法,它替代了AMD中require(【】,factory)的用法。

define:用来定义模块。

define(function(require, exports, module) {

  // 模块代码

});

require: 用来获取指定模块的接口。

define(function(require) {

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

require.async:用来在模块内部异步加载一个或多个模块。

define(function(require) {

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

exports: 用来在模块内部对外提供接口

define(function(require, exports) {

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

module.exports:与 exports 类似,用来在模块内部对外提供接口

define(function(require, exports, module) {

  // 对外提供接口
module.exports = {
name: 'a',
doSomething: function() {};
}; });

seajs API具体的用法,请参考seajs API

7、CMD和AMD之间的差异

1)AMD(异步加载模块),CMD(通用模块),AMD是需要通过异步加载的形式把依赖加载进来,然而CMD在require依赖的时候,可以通过同步的形式(require),也可以通过异步的形式(require.async)。当然AMD也可以通过特殊的写法支持CMD,但是不推崇。

2)CMD 推崇依赖就近,AMD 推崇依赖前置。在AMD中,我们需要把依赖前置在依赖数组中。而在cmd中,我们只需要在使用这个模块前,把依赖的模块require进来。

3)设计理念不一样,在 SeaJS 里,API 的设计理念是:

  • 保持简单,职责单一。
  • 遵守规范,但不拘泥。
  • 适度灵活

requirejs中,require的用法多样,比如:

require('a')  -- gets exports of module a

require(['a']) -- fetch module a according to module name scheme

require(['a.js'])  -- fetch a.js directly relative to current page

require({...})  -- set loader config

4)聚焦点有差异

seajs专注于浏览器环境下的模块加载,而requirejs集成了在node环境以及Rhino 环境下的代码,这导致requirejs比seajs更大。

参考文献:

commonjs规范

CommonJS Modules/1.0 规范 & AMD 规范中文版

AMD规范

requirejs中文文档

requirejs英文文档

CMD规范

seajs文档

seajs和requirejs的异同

注:本人见识短浅,有不够准确的地方万望指正。拜谢!

关于commonjs,AMD,CMD之间的异同的更多相关文章

  1. CommonJS, AMD ,CMD之间的关系

    commonjs是用在服务器端的,同步的,如nodejs amd, cmd是用在浏览器端的,异步的,如requirejs和seajs 其中,amd先提出,cmd是根据commonjs和amd基础上提出 ...

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

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

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

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

  4. JS JavaScript模块化(ES Module/CommonJS/AMD/CMD)

    前言 前端开发中,起初只要在script标签中嵌入几十上百行代码就能实现一些基本的交互效果,后来js得到重视,应用也广泛起来了, jQuery,Ajax,Node.Js,MVC,MVVM等的助力也使得 ...

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

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

  6. 研究一下javascript的模块规范(CommonJs/AMD/CMD)

    最近写react需要使用nodejs作为开发环境,需要通过npm安装一些第三方的依赖库,因此慢慢感觉到nodejs基础薄弱对我带来了一些不安全感,尤其是javascript模块这一块听到了很多概念,比 ...

  7. commonjs AMD,CMD

    CommonJS CommonJs 是服务器端模块的规范,Node.js采用了这个规范. 根据CommonJS规范,一个单独的文件就是一个模块.加载模块使用require方法,该方法读取一个文件并执行 ...

  8. 插件兼容CommonJS, AMD, CMD 和 原生 JS

    模块标准 CommonJS CommonJS 有三个全局变量 module.exports 和 require.但是由于 AMD 也有 require 这个全局变量,故不使用这个变量来进行检测. 如果 ...

  9. 关于 CommonJS AMD CMD UMD 规范的差异总结

    一.CommonJS 主要是用于服务器端的规范,比如目前的nodeJS. 根据CommonJS规范,一个单独的文件就是一个模块.每一个模块都是一个单独的作用域,也就是说,在一个文件定义的变量(还包括函 ...

  10. commonjs amd cmd的区别

    一篇博客告诉你三者的区别:http://zccst.iteye.com/blog/2215317 告诉你三者同requirejs seajs的区别:http://blog.chinaunix.net/ ...

随机推荐

  1. Linux 6.4 partprobe出现warning问题

    今天在给服务器做LVM的时候(服务器的系统是CentOS 6.3),用fdisk分区之后,用w写入分区表的时候,就提示Command (m for help): wThe partition tabl ...

  2. bootstrap - btn 实例

    <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...

  3. bootstrap 字体图标

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. Linux系统(二)软件的安装与卸载

    序言 上一篇我们了解啦Linux系统中,根目录下的各个文件夹是做什么用的啦,也学会文件如何压缩打包.那么接下来我们就该用到这个系统啦.用这个系统,就是用这个系统的软件,那么我们对我们需要的软件如何安装 ...

  5. 理解WEB API网关

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  6. Keepalived安装与配置

      下载并解压Keepalived安装包到两台nginx所在的服务器   192.168.200.1   192.168.200.2     执行编译安装(安装目录设置为 /usr/local/kee ...

  7. 学习笔记——Java包装类

    由于Java不能定义基本类型的对象,所以通过包装类提供了各种可用方法的封装. 这一部分的知识,需要能够通过查看Integer.Boolean.Byte.Character.Double.Number类 ...

  8. java Log日志规范

    Overview 一个在生产环境里运行的程序如果没有日志是很让维护者提心吊胆的,有太多杂乱又无意义的日志也是令人伤神.程序出现问题时候,从日志里如果发现不了问题可能的原因是很令人受挫的.本文想讨论的是 ...

  9. 微软.NET年芳15:我在Azure上搭建Photon服务器(C#.NET)

    网上火热的“微软.NET年芳15”文章,我也得写点什么嘛,毕竟我还是现任的微软MVP. 摘录网上的“.NET 15周年”信息如下: 微软的 .NET 框架本周迎来了 15 岁生日..NET 的第一个版 ...

  10. ERP库位分布看板(库位管理)

    客户正在使用的看板管理,根据厂家需求,做的二次开发. 一:看板效果 二:客户需求 1.客户需求:XX是一家汽车零部件(胶管,硅胶管等)的生产厂家,因此对原料,半成品的有效期有严格的要求. 多次调研得知 ...