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. 最简单的排序算法之一冒泡排序----js实现

    1. 算法步骤 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.这步做完后,最后的元素会是最大的数. 针对所有的元素重复以上的步骤, ...

  2. 关于post与get请求参数存在特殊字符问题

    遇到项目中存在文本编辑框输入特殊字符 比如:# ? & 空格 , 导致后台接受不到参数问题,对可能存在特殊字符的参数进行encodeURIComponent; C#后台接受参数不需要解码 也可 ...

  3. Bootstrap记录

    左侧 导航下拉: <li class="dropdown"> <a href="#" class="dropdown-toggle& ...

  4. C语言程序_管理系统

    #include <stdio.h> #include <stdlib.h> #include <string.h> #define N 3 #define LEN ...

  5. PB程序“无法启动此程序,因为计算机中丢失PBvm90.dll。尝试重新安装该程序以解决此问题”的解决方法

    因为有计算机自考科目,要求使用PB程序做一个管理系统.昨天刚安装好了PB程序,今天使用的时候,当我打开一个PB程序时,出现了"无法启动此程序,因为计算机中丢失PBvm90.dll.尝试重新安 ...

  6. Alamofire源码解读系列(三)之通知处理(Notification)

    本篇讲解swift中通知的用法 前言 通知作为传递事件和数据的载体,在使用中是不受限制的.由于忘记移除某个通知的监听,会造成很多潜在的问题,这些问题在测试中是很难被发现的.但这不是我们这篇文章探讨的主 ...

  7. 开始了大概三四天的Rails学习之路

    最近因为一位极光推送朋友,我开始了大概三四天的Rails学习之路,最终达到的水平是可以比较轻松地做出大部分功能,然后自我感觉可以自如地按照Rails的设计思想去思考.由于编程的日益流行,我结识了越来越 ...

  8. 数字化工厂ERP解决方案

    数字化工厂 数字化工厂建设,在现有基础上提升,实现管理层对订单进度.生产绩效.产能分析.质量管理.产品追溯和存货管理等提供业务分析报告:在控制层有可视化看板.移动客户端实现对生产状态的实时掌控,快速处 ...

  9. WCF小试

    1.创建WCF 右键解决方案-新建项目-WCF服务应用程序. 创建后会生成一些文件,其中IService.cs是服务的接口,只有在接口中定义的方法才能被外部调用,Service.svc是我们的服务名称 ...

  10. Java原生API操作XML

    使用Java操作XML的开源框架比较多,如著名的Dom4J.JDOM等,但个人认为不管你用那个框架都要对JDK原生的API有所了解才能更得心应手的应用.本篇就来简单了解下原生的XML API. JAV ...