JS 模块化规范
在ES6出现之前,JS语言本身并没有提供模块化能力,这为开发带来了一些问题,其中最重要的两个问题应当是全局污染和依赖管理混乱...
模块化的目的
在ES6出现之前,JS语言本身并没有提供模块化能力,这为开发带来了一些问题,其中最重要的两个问题应当是全局污染和依赖管理混乱。
<script src="a.js"></script>
<script src="b.js"></script>
例如上述代码引入2个js,但是如果a和b两个js文件中存在同名的全局变量或者方法,就会对接下来的使用造成混乱和错误。因此我们需要引入模块化的规范来减少依赖和混乱。
常见的模块化规范
CommonJS
最大的特征就是module.exports和require。
// a.js
exports = function(x) {
console.log(x);
};
// b.js
var A = require('a.js');
A("Hello"); // Hello
exports = module.exports
总结来说规范基本是以下4条:
- 每个文件都是一个模块;
- 在模块内提供module对象,表示当前模块;
- 模块使用exports对外暴露自身的函数/对象/变量等;
- 模块内通过
require()
方法导入其他模块;
AMD
对外暴露模块使用了define函数:
// file a.js
define('moduleName', ['other.js', 'another.js'], function() {
// code...
})
可以看到define包含3个参数:
- moduleName,该参数可以省略,表示该模块的名字,一般作用不大
- ['name1', 'name2'],第二个参数是一个数组,表示当前模块依赖的其他模块,如果没有依赖模块,该参数可以省略
- callback,第三个参数是必传参数,是一个回调函数,内部是当前模块的相关代码
另外,AMD规定在运行当前加载模块回调前,会首先将所有依赖包加载完毕,也是就是define函数的第二个参数中指定的依赖包。
CMD
CMD名字与AMD类型,他们的模块定义方式也是类似:
define(function(require, exports, module) {
var a = require('./a')
a.doSomething();
// code...
var b = require('./b')
// code...
})
区别在于依赖的加载机制,AMD是依赖前置,就是在当前模块前就把依赖加载好,CMD则是就近原则,意味着在需要的时候加载依赖。
UMD
又是一个类似名称的叫UMD,它是CommonJS、AMD、CMD的一种兼容体,可以兼容上述3种模式。
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
//AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
//Node, CommonJS之类的
module.exports = factory(require('jquery'));
} else {
//浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
//方法
function myFunc(){};
//暴露公共方法
return myFunc;
}));
ES6 modules
上面👆的几种模式都是JS社区提出来的,可以说是来自民间贡献,而ES6出来后,官方也带来了JS的模块化功能。
ES6中的模块化能力由两个命令构成:export和import,export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
// a.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
// b.js
import {firstName, lastName, year} from 'a.js'
console.log(firstName); // Michael
console.log(lastName); // Jackson
console.log(year); // 1958
year = 2019; // Syntax Error : 'a' is read-only;
在的例子中,可以看到最后我们试图修改引自a.js的year变量会报错,因为这个引入的值是可读的,不可修改。
另外,我们还可以使用 export default 来暴露一个默认的变量,这样的好处是我不需要知道此模块暴露出的变量名/函数名就可以使用。
// a.js
function print(x) {
console.log(x);
}
export default print;
// b.js
import Print from 'a.js'
Print("Hello"); // Hello
Print 是我自定义的名字,export default 在每个模块中只能执行一次,其本质是导出了一个名为default的变量或函数。