JS中的模块规范(CommonJS,AMD,CMD),如果你听过js模块化这个东西,那么你就应该听过或CommonJS或AMD甚至是CMD这些规范咯,我也听过。如果仅仅是一些基础的活,我们熟悉它的使用就好,但是我们不能对一块只是朦朦胧胧,今天我就来整理一下,这些到底是什么?它们在我们前端的领域到底起了怎样的作用?
CommonJs
CommonJS就是为JS的表现来制定规范,NodeJS是这种规范的实现,webpack 也是以CommonJS的形式来书写。因为js没有模块的功能所以CommonJS应运而生,它希望js可以在任何地方运行,不只是浏览器中。它的终极目标是提供一个类似Python,Ruby和Java标准库。
如果你在Node.js平台上写过东西,你应该会比较熟悉CommonJS规范。与前面的AMD及CMD规范不一样的是,CommonJS规范一般应用于服务端(Node.js平台),而且CommonJS加载模块采用的是同步方式(这跟他适用的场景有关系)。同时,得力于 Browserify 这样的第三方工具,我们可以在浏览器端使用采用CommonJS规范的js文件。
1 2 3 4 5 | CommonJS定义的模块分为:{模块引用(require)} {模块定义(exports)} {模块标识(module)} require()用来引入外部模块; exports对象用于导出当前模块的方法或变量,唯一的导出口; module对象就代表模块本身。 |
虽说Node遵循CommonJS的规范,但是相比也是做了一些取舍,填了一些新东西的。
不过,说了CommonJS也说了Node,那么我觉得也得先了解下NPM了。NPM作为Node的包管理器,不是为了帮助Node解决依赖包的安装问题嘛,那它肯定也要遵循CommonJS规范啦,它遵循包规范(还是理论)的。
CommonJS主要是为了JS在后端制定的,那它为什么不适合前端呢?
那我们就来看看它的原理:
1 2 3 4 5 | 浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量。 module exports require global |
只要能够提供这四个变量,浏览器就能加载 CommonJS 模块。
知道了原理,就可以做出工具,Browserify是目前最常用的 CommonJS 格式转换的工具。(有兴趣的同学可以去深入了解)
AMD
基于commonJS规范的nodeJS出来以后,服务端的模块概念已经形成,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。看如下代码,如果在浏览器中运行,会有一个很大的问题,你能看出来吗?
1 2 | var math = require('math'); math.add(2, 3); |
第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。您会注意到 require是同步的。这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。
因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。
CommonJS是主要为了JS在后端的表现制定的,他是不适合前端的,AMD(异步模块定义)出现了,它就主要为前端JS的表现制定规范
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
1 | require([module], callback); |
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:
1 2 3 | require(['math'], function (math) { math.add(2, 3); }); |
math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js。
require.js的诞生,就是为了解决这两个问题:
(1)实现js文件的异步加载,避免网页失去响应;
(2)管理模块之间的依赖性,便于代码的编写和维护。
当然我觉得每个库的诞生都有它自身的价值。
AMD就只有一个接口:define(id?,dependencies?,factory);
它要在声明模块的时候制定所有的依赖(dep),并且还要当做形参传到factory中,像这样:
1 | define(['dep1','dep2'],function(dep1,dep2){...}); |
要是没什么依赖,就定义简单的模块,下面这样就可以啦:
1 2 3 4 5 | define(function(){ var exports = {}; exports.method = function(){...}; return exports; }); |
咦,这里有define,把东西包装起来啦,那Node实现中怎么没看到有define关键字呢,它也要把东西包装起来呀,其实吧,只是Node隐式包装了而已.....
这有AMD的WIKI中文版,讲了很多蛮详细的东西,用到的时候可以查看:AMD WIKI 中文版
CMD
CMD规范,全称”Common Module Definition”,称为 通用模块加载规范 。一般也是用在浏览器端。浏览器端异步加载库 Sea.js 实现的就是CMD规范。
AMD/CMD区别,虽然都是并行加载js文件,但还是有所区别,AMD是预加载,在并行加载js文件同时,还会解析执行该模块(因为还需要执行,所以在加载某个模块前,这个模块的依赖模块需要先加载完成);而CMD是懒加载,虽然会一开始就并行加载js文件,但是不会执行,而是在需要的时候才执行。
在 CMD 规范中,一个模块就是一个文件
1 | define(id?, deps?, factory) |
1 2 3 | id:模块标识 deps:模块依赖 factory:可以是一个函数,也可以是一个对象或字符串。 |
具体更详细的规范,可以参考CMD规范
有什么遗漏的,欢迎补充~