代码的模块化,一个模块就是一个文件,使用脚本时提倡以module ID替代URL地址。模块不同于传统的脚本文件,它良好地定义了一个作用域来避免全局名称空间污染。它可以显式地列出其依赖关系,并以函数(定义此模块的那个函数)参数的形式将这些依赖进行注入,而无需引用全局变量。
script标签的js下载是异步的,解析是同步的。async主要是指定异步解析,仅能用于不依赖于其他js文件的js文件。
1、CommonJS
CommonJS定义的模块分为:模块引用(require)、 模块定义(exports)、 模块标识(module)。
// a.js
const b = require('./b');
//b.js
module.exports = {
bb: 'bbb'
}
或
exports.bb = 'bbb';
CommonJS 的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。exports是module的一个属性,如下module的值
Module {
id: '.',
exports: {},
parent: null,
filename: '/xmpp/module.js',
loaded: false,
children:[ Module {
id: '/xmpp/module2.js',
exports: [Object],
parent: [Circular],
filename: '/xmpp/module2.js',
loaded: true,
children: [],
paths: [Array] } ],
paths:[
'/xmpp/node_modules',
'/node_modules',
'/node_modules' ]
}
require是同步的,对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。CommonJS是主要为了JS在后端的表现制定的,它是不适合前端的。
NodeJS是CommonJS规范的实现,webpack 也是以CommonJS的形式来书写
参考文章,CommonJS
2、AMD 模块规范
AMD(Asynchronous Module Definition)是异步模型规范。浏览器端的模块,不能采用同步加载,只能采用异步加载,这就是AMD规范诞生的背景。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
模块引用,代码如下:
require([module], callback);
require(['math'], function (math) {
math.add(2, 3);
});
模块的定义,代码如下:
define(id?, dependencies?, factory);
dependencies表示该模块依赖的其他所有模块标识,模块依赖必须在真正执行具体的factory方法前解决,AMD 运行时核心思想是「Early Executing」,也就是提前执行依赖
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});
模块导出,代码如下:
define(function (require, exports, module) {
var a = require('a'),
b = require('b');
exports.action = function () {};
});
参考文章,AMD AMD 的 CommonJS wrapping
3、CMD 模块规范
CMD(Common Module Definition)是公共模块规范。在 CMD 规范中,推崇 as lazy as possible。
模块定义,代码的书写格式如下:
define(factory);
define(function(require, exports, module) {
// 同步加载模块
var a = require('./a');
a.doSomething();
});
另外一种用法
define(id?, deps?, factory)
字符串 id 表示模块标识,数组 deps 是模块依赖,如下:
define('hello', ['jquery'], function(require, exports, module) {
// 同步加载模块
var a = require('./a');
a.doSomething();
// 异步加载多个模块,在加载完成时,执行回调
require.async(['./c', './d'], function(c, d) {
c.doSomething();
d.doSomething();
});
});
注意:带 id 和 deps 参数的 define 用法不属于 CMD 规范,而属于 Modules/Transport 规范。
模块导出方式
define(function(require, exports, module) {
//方法一,通过 return 直接提供接口
return {
foo: 'bar',
doSomething: function() {}
};
//方法二,module.exports
module.exports = {
foo: 'bar',
doSomething: function() {}
};
//方式三,exports
exports.foo = 'bar';
exports.doSomething = function() {};
});
参考文章:CMD 模块定义规范
4、AMD 和CMD的区别
CMD 推崇依赖就近,AMD 推崇依赖前置
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b')
// 依赖可以就近书写
b.doSomething()
// ...
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) {
// 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
//...
})
参考文章,AMD 和 CMD 的区别有哪些?
5、SeaJS 与 RequireJS区别
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。 AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
参考文章, RequireJS官方文档
LABjs、RequireJS、SeaJS 哪个最好用?为什么?
6、ES6模块
ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。
动态加载,只有运行时才能得到这个_fs对象
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
静态加载,ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
// ES6模块
import { stat, exists, readFile } from 'fs';
静态加载的好处:
- 可以进行类型检测
- 不再需要对象作为命名空间
- 不再必须做成全局变量