js模块化进程
js的模块化进程
现在前端技术日新月异,对于同一个问题痛点,各个时段有各自的解决方案,这就带来了很大差异。今天我就打算梳理js模块化的历史进程,讲一讲这些方案要做什么,怎么做。
js模块化进程的起因
现今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。当一个项目开发的越来越复杂的时候,你会遇到一些问题:命名冲突(变量和函数命名可能相同),文件依赖(引入外部的文件数目、顺序问题)等。
JavaScript发展的越来越快,超过了它产生时候的自我定位。这时候js模块化就出现了。
什么是模块化
模块化开发是一种管理方式,是一种生产方式,一种解决问题的方案。他按照功能将一个软件切分成许多部分单独开发,然后再组装起来,每一个部分即为模块。当使用模块化开发的时候可以避免刚刚的问题,并且让开发的效率变高,以及方便后期的维护。
js模块化进程
一、早期:script标签
这是最原始的 JavaScript 文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在 window 对象中。
缺点:
1.污染全局作用域
2.只能按script标签书写顺序加载
3.文件依赖关系靠开发者主观解决
二、发展一:CommonJS规范
允许模块通过require方法来同步加载(同步意味阻塞)所要依赖的其他模块,然后通过module.exports来导出需要暴露的接口。
// module add.js
module.exports = function add (a, b) { return a + b; }
// main.js
var {add} = require('./math');
console.log('1 + 2 = ' + add(1,2);
CommonJS 是以在浏览器环境之外构建JavaScript 生态系统为目标而产生的项目,比如在服务器和桌面环境中。
三、发展二:AMD/CMD
(1)AMD
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出(异步模块定义)。
AMD标准中定义了以下两个API:
- require([module], callback);
- define(id, [depends], callback);
require接口用来加载一系列模块,define接口用来定义并暴露一个模块。define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 a.add1() ... b.add2() ... })
优点:
1、适合在浏览器环境中异步加载模块 2、可以并行加载多个模块
(2)CMD
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。(在CommomJS和AMD基础上提出)
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.add1();
...
if (status) {
var b = requie('./b');
b.add2();
}
});
优点:
1、依赖就近,延迟执行 2、可以很容易在服务器中运行
(3)AMD 和 CMD 的区别
AMD和CMD起来很相似,但是还是有一些细微的差别:
1、对于依赖的模块,AMD是提前执行,CMD是延迟执行。
2、AMD推崇依赖前置;CMD推崇依赖就近,只有在用到某个模块的时候再去require。
3、AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一
四、发展三:ES6模块化
EcmaScript6 标准增加了JavaScript语言层面的模块体系定义(关键字)。
在 ES6 中,我们使用export关键字来导出模块,使用import关键字引用模块。
// module math.jsx
export default class Math extends React.Component{}
// main.js
import Math from "./Math";
ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
即 CommonJS 先加载整个模块,输出一个对象,取对象内相应的值,输出后内部不会再变化;ES6 是静态编译命令,先加载一个引用,等执行时再根据引用到加载模块内取值输出,动态引用不缓存。
目前很少JS引擎能直接支持 ES6 标准,因此 Babel 的做法实际上是将不被支持的 import 翻译成目前已被支持的 require。