什么是CommonJS、AMD和CMD
CommonJS、AMD和CMD都是js的模块加载方案,JS在最初设计的时候,并没有模块这种概念,也没有提供将各模块进行灵活组装的机制,有的同学说,标签不就可以吗?
是可以帮我们把代码组装起来,但是功能太薄弱,而且因为是标签,所以只能在浏览器里面用,对于我们的nodejs就无能为力了,所以算不上模块解决方案。
后来nodejs出现之后,js可以横跨前后两端进行开发,模块成了不得不解决的一个问题,一些技术爱好者组成了技术社区,社区的推动下,制定了CommonJS模块的规范,nodejs依照此规范,实现了自己的模块加载机制。
CommonJs有啥特点
在CommonJS中,有一个全局性方法require(),用于加载模块。比如我们想要操作文件,就需要fs
模块:
const fs = require('fs')
fs.readFile('a.txt', 'utf8', function(err, data) {
console.log(data)
})
CommonJS是在运行时加载模块的,因为CommonJS认为模块就是对象:
// CommonJS模块
const { stat, exists, readFile } = require('fs');
// 等同于
const _fs = require('fs');
const stat = _fs.stat;
const exists = _fs.exists;
const readfile = _fs.readfile;
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”,比如语法分析和类型检验,这是CommonJS的一大不足。
CommonJS的另外一个不足是模块的加载都是同步的,也就是说:
const fs = require('fs')
console.log('hello world')
只有等到fs模块加载完成之后,后面的代码才有机会执行,哪怕后面并没有用到fs
模块。
这在服务器端没什么问题,但是在浏览器上却行不通,因为浏览器加载模块的时候,需要通过网络,如果网络出现异常,模块加载卡住,后面的代码就得不到运行,浏览器也会陷入假死状态,我们希望当模块加载失败的时候,一些跟该模块无关的代码依然可以运行,这就需要一种新的模块加载机制——AMD。
什么是AMD
AMD也是搞CommonJs的那帮人搞出来的,据说这帮人搞出来的CommonJs在nodejs上效果显著,于是雄心勃勃的向浏览器挺近,但是在内部遇到了很大的分歧,最后形成了三个流派,其中一个流派就推出了AMD规范,AMD的全称是“Asynchronous Module Definition”,即“异步模块定义”,从名字上可以看出,它加载模块的方式是异步的,AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
require([module], callback);
第一个参数[module]
,是一个数组,里面的成员就是要加载的模块;第二个参数callback
,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:
require(['fs'], function(fs) {
// to do something
})
console.log('hello world')
这样模块fs
的加载,就不会影响console.log
的执行,程序相对来说更坚挺。
AMD有个缺陷,来看下面这段代码:
const env = 'dev'
require(['fs', 'path'], function(fs, path) {
if (env === 'dev') {
fs.unlink('./a.txt', err => console.log('文件删除成功!'))
}
if (env === 'test') {
console.log(path.sep)
}
})
console.log('hello world')
可以看出,虽然代码加载了fs
和path
两个模块,但是在程序正真执行的过程中,有些模块的加载其实是没有必要的,即使加载了,在之后的回调中也没有被用到,这样在一定程度上造成了浪费。
如何解决AMD的缺陷
为了解决这个问题,阿里巴巴的一个叫玉伯的前端开发,提出了CMD规范(Common Module Definition),并根据此规范,写了一个专门的模块加载器——sea.js,sea在英文中是大海的意思,这也表达了作者对这个加载器的态度——海纳百川,有容乃大。
与sea起名的是AMD下的一个加载器,叫RequireJS。
sea在加载模块时也是异步的,它主要的特点是允许你在使用模块的时候才去加载模块:
const env = 'dev'
define(function(require, exports, module) {
if (env === 'dev') {
const fs = require('fs')
fs.unlink('./a.txt', err => console.log('文件删除成功!'))
}
if (env === 'test') {
const path = require('path')
console.log(path.sep)
}
})
console.log('hello world')
编写模块要注意什么
我们在设计模块的时候,需要尽可能的做到高内聚,低耦合。
高内聚实际上就是要求我们在开发的时候,遵守单一职责原则,也就是一个模块,里面的各个功能元素(类或者函数)应该只做好自己的本职工作,千万不要当老好人,什么都想搭把手。一个旅游团,导游负责景点讲解,司机负责开车,游客负责卖萌拍照,每个人各司其职,旅游就能有条不紊的进行下去,但是,如果游客想帮司机开车,司机也想替游客讲两个景点故事,虽然是好心,但是容易办坏事。
低耦合主要是之模块与模块之间的关系尽可能的简单,如果一个模块需要修改,应该尽可能的不会影响到其他模块,避免发生牵一发而动全身的情况,这样本身也能极大的节约我们的时间。
模块的价值是什么
模块最大的价值是规范和封装了代码,提高了代码的复用性,只要遵守模块的规则,写出来的模块就可以被各个系统使用,提高代码的流动性,可以说,模块是代码世界的集装箱,通过将代码封装成模块,可以很方便的运输到各个行业的各个系统中运行,从而极大的放大了代码的价值。
其中,github模块的仓库,是伟大码农的智慧的结晶,也是世界上(可能)最大的基友交友平台。