之前写过一篇模块化博客,那是掘金的第一篇,还是对各个规范之间的差异不懂,查阅了一些资料,再次总结下。在各个标准规范出来之前,手写的模块系统无非就是使用闭包,然后暴露出来接口,上一篇中归纳过,就暂不赘述,接下来总结一些各个规范。
CommonJS
CommonJS规范概述了同步声明依赖的模块定义。这个规范主要用于在服务器端实现模块化代码组织,如果想要使用在浏览器端,需要转译下。
CommonJS模块定义需要使用require()指定依赖,exports对象定义公开的API。
module.exports = {
name:'nick'
}
const a = require('./a')
其主要有以下几个特点
-
无论一个模块在require()中引用多少次,模块永远是单例。
-
模块第一次加载后会被缓存,后续加载的只是其缓存
-
require()加载是同步执行操作,可以包含在语句中
if(true){ require('./a') } -
exports与module.exports
exports只是module.exports的一个引用,
-
exports
可以一次导出多个实体
exports.name = 'nick' exports.age = '18' //等同于 module.exports = { name:'nick', age:'18' } -
module.exports
如果一次只想导出一个实体,可以直接给module.exports赋值
module.exports = { name:'nick' }
-
AMD
AMD( Asynchronous Module Definition ),以浏览器为目标执行环境。AMD的一般策略是让模块申明自己的依赖,而运行在浏览器中的模块系统会按需获取依赖,并在依赖完成加载后立即执行依赖的模块。
AMD模块实现的核心是用函数包装模块定义,这样可以防止申明全局变量,并允许加载器控制何时加载模块。包装模块的函数也便于模块代码的移植,因为包装函数内部的所有模块代码使用的都是原生JavaScript结构。包装模块的函数是全局define的参数,譬如:
//id为moduleA的模块定义
//moduleA依赖moduleB
define('moduleA',['moduleB'],function(moduleB){
return{...}
})
其内部也支持require和exports对象
define('moduleA',['moduleB'],function(require,exports){
let moduleB = require('moduleB')
exports.stuff = moduleB.dostuff()
})
UMD
为了统一CommonJS和AMD生态,通用模块(UMD)应用而生。UMD可以用于创建这两个系统都可以使用的模块代码。UMD定义的模块会在启动时检测要使用的模块系统然后进行适配。
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['b'], factory);
} else {
// Browser globals
root.amdWeb = factory(root.b);
}
}(typeof self !== 'undefined' ? self : this, function (b) {
// Use b in some fashion.
// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
}));
CMD
CMD规范整合了CommonJS和AMD规范的特点,其全称为 Common Module Definition ,与require.js类似,其规范的实现为sea.js。
AMD和CMD的两个主要的差别:
- AMD需要异步加载模块,而CMD在加载模块时,可以通过同步的的形式(require),也可以通过异步的形式(require.async)。
- CMD遵循依赖就近原则,AMD遵循依赖前置原则。在AMD中,我们需要把模块所需要的依赖都提前申明在依赖数组中;而CMD中,我们只需要在具体代码逻辑内,在使用前引入依赖即可。
//AMD
define(['./a','./b'], function (a, b) {
//依赖一开始就写好
a.test();
b.test();
});
//CMD
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.test();
...
//软依赖
if (status) {
var b = requie('./b');
b.test();
}
});
ES6模块
ES6最大的一个改进就是引入了模块规范,这个规范全方位简化了之前出现的模块加载器,原生浏览器支持就意味着加载器及其预处理器都不在必要,可以看是AMD和CommonJS的集大成者。
模块标签及定义
带有type='module'属性的的标签后会立即下载模块文件,但执行会延迟到文档解析完成。与
如果给模块标签添加async属性,不仅模块执行顺序不再与
模块行为
ES6模块借用了CommonJS和AMD很多优秀的特性
- 模块代码只在加载后执行
- 模块是单例的
- 模块只会加载一次
- 模块可以请求加载其他模块
- 支持循环依赖
也添加了一些新的行为
- 默认只在严格模式下执行
- 模块不共享全局命名空间
- 模块顶级this值是undefined
- 模块异步加载执行
模块导出
ES6模块导出支持命令导出和默认导出。
-
命令导出
命令导出就好像模块是被导出的容器
const foo = 'foo' export {foo} export {foo as f} //提供别名 -
默认导出
默认导出就好像模块与被导出的值是一回事。使用default关键字将一个值的申明为默认导出,每个模块只能有一个默认导出。
const foo = 'foo' export default foo命令导出与默认导出并不会冲突
const foo = 'foo' const bar = 'bar' export default foo export {bar} //等同于 export {bar,foo as default}
模块导入
使用import关键字来导入模块,而且改关键字必须出现在模块的顶级。
if(true){
import ...
export ...
}
//都会报错
import后面跟的必须是纯字符串,可以是相对路径,也可以是绝对路径
import ... from '/a.js'
导入的模块是只读的,相当于const申明的变量一样,只能更改其属性。
import foo ,* as Foo from '/foo.js'
foo = 'boo' //报错
Foo.name = 'nick' //允许
命令导出可以使用*批量获取并赋值给相应的变量
export {bar,foo}
import * as Foo from '/foo.js'
Foo.bar
Foo.foo
默认导出就好像整个模块就是导出的值
import {default as foo} from './foo.js'
//等同与
import foo from './foo.js'