一、 CommonJS规范
我在前边介绍nodejs的文章中提到过模块这个概念,模块就是实现了一定功能的js文件,比如前边使用的sum.js就可以看做是一个模块。
那么为什么需要模块呢?
因为使用模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载对应的模块。但是,编写模块需要我们遵守共同的约定,否则你有你的写法,我有我的写法,就乱套了!
约定?
既然要遵守相同的约定,那么都有哪些约定呢?就是本次要介绍的CommonJS和AMD和CMD,以及es6里面的import和export
首先,要明白,CommonJS和AMD和CMD是约定,是思想,既然是约定,是思想,它就是虚的,就像java里面的接口,并不是真正的实现,这三种规范都有实现了自己的"产物".我们先来看看CommonJS.
CommonJS在js的应用领域剑走偏锋,它不相信js只能运行在浏览器中,也不满足js只能运行在浏览器中,因此CommonJS定义了很多普通应用程序(主要指非浏览器的应用)使用的API,并希图提供类似后端语言的功能,以便让开发人员基于CommonJS API编写应用程序,然后这些应用可以运行在不同的JavaScript解释器和不同的宿主环境里。
功夫不负有心人,是金子总会发光的,大名鼎鼎的nodejs产生了,将javascript语言用于服务器端编程,而NodeJS就是CommonJS规范的实现,webpack 也是以CommonJS的形式来书写。
这里再说一下为什么模块必须要有?
在浏览器环境下编写javascript,没有模块其实也不是特别大的问题,因为毕竟网页程序的复杂性有限,不使用模块也能完成;但是在服务器端,不使用模块化编程的思想就很难了,因为凡是涉及到服务端的编程一般都不会太简单。
我们前边说了那么多nodejs,你可能不知道nodejs在哪里应用到了CommonJS规范,但是你一定在nodejs中见过CommonJS的这些规范,这样是不是可以称作“只见其面,不闻其名”了。
在CommonJS中,CommonJS定义的模块分为:
require:模块的引用
exports:模块的定义(导出) ,用于导出当前模块的方法或变量
module:模块的标识(也用于模块导出),module对象就代表模块本身。
大家回忆一下,我们是不是在nodejs中都使用过了,对吧,就像一些成语典故,我们常常挂在嘴边,却不知道它的来历,这次知道nodejs中的require、exports、module出自何处了吧,就是出自CommonJS规范。
二、amd规范
基于commonJS模块化规范的nodeJS问世之后,服务端的模块概念已经形成,接下来大家考虑到的自然就是客户端模块了。那么能不能复制CommonJS规范在服务端的应用,把CommonJS应用于客户端浏览器环境呢?比如
var math = require('math');
math.add(2, 3);
这是符合CommonJS规范的代码,math.add(2, 3)在在第一行require('math')之后运行,因此必须等math.js加载完成才能执行下面的代码。也就是说,CommonJS里面的require 是同步的。这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器可能会处于"假死"状态。
基于上边的原因,客户端(浏览器)的模块化不能使用同步化的CommonJS,那么就只能采用"异步加载"(asynchronous)了。在这个大形势下,AMD规范就应运而生了。
AMD是"Asynchronous Module Definition"的缩写,翻译过来就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
require([module], callback);
参数说明:
[module]:是一个数组,里面的成员就是要加载的模块。
callback:是加载成功之后的回调函数
把上面的代码改写成AMD形式,就是下面这样:
require(['math'], function (math) {
math.add(2, 3);
});
实现了amd规范的前端框架有require.js和curl.js。
三、cmd规范
有个框架叫sea.js,也被称作seajs,就是遵循cmd模块化编程规范开发出来的,作用与requirejs是一样的,都是js文件的加载器。下面是官方对seajs和requirejs的比较:
相同之处
RequireJS 和 Sea.js 都是模块加载器,倡导模块化开发理念,核心价值是让 JavaScript 的模块化开发变得简单自然。
不同之处
两者的主要区别如下:
-
定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。Sea.js 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 环境中。
-
遵循的规范不同。RequireJS 遵循 AMD(异步模块定义)规范,Sea.js 遵循 CMD (通用模块定义)规范。规范的不同,导致了两者 API 不同。Sea.js 更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
-
推广理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。Sea.js 不强推,采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
-
对开发调试的支持有差异。Sea.js 非常关注代码的开发调试,有 nocache、debug 等用于调试的插件。RequireJS 无这方面的明显支持。
-
插件机制不同。RequireJS 采取的是在源码中预留接口的形式,插件类型比较单一。Sea.js 采取的是通用事件机制,插件类型更丰富。
总之,如果说 RequireJS 是 Prototype 类库的话,则 Sea.js 致力于成为 jQuery 类库。
以前用过一个类似于easyui的前端框架叫做bui,官网是http://www.builive.com,使用风格有一种seajs,大家可以参考下:
如下是相关的js代码:
<script src="http://g.tbcdn.cn/fi/bui/jquery-1.8.1.min.js"></script>
<script src="http://g.alicdn.com/bui/seajs/2.3.0/sea.js"></script>
<script src="http://g.alicdn.com/bui/bui/1.1.21/config.js"></script>
<!-- script start -->
<script type="text/javascript">
BUI.use('bui/calendar',function(Calendar){
var datepicker = new Calendar.DatePicker({
trigger:'.calendar',
autoRender : true
});
});
</script>
其实js模块化编程规范写了这么多,大家只需要知道有这么3个规范以及这3个规范的典型实现就可以了。我们在学的是遵循着commonjs规范的nodejs,并没有一定要学会requirejs和seajs才能学会nodejs的说法。