前端为什么需要模块化?
随着前端能承担更多的开发责任,各种问题开始凸显,全局变量冲突、依赖关系难以管理、数据安全问题等等。
什么是模块?
- 将一个复杂的程序依据一定的规则封装成几个块,并进行组合在一起。
- 块的内部数据与实现是私有的,只是向外部暴露一些接口(方法)与外部其它模块通信。
模块化的好处?
- 避免命名冲突(减少命名空间污染)
- 更多的分离,按需加载
- 更高复用性
- 高可维护性
那么模块化规范有哪些呢?
模块化规范
CommonJS
CommonJS
是服务器端模块的规范,Node.js
采用了这个规范。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。
(1)、基本语法
// 定义模块math.js
const value = 1
const add = function(){
value++
}
module.exports = {
value,
add
}
// 引入模块math.js
const math = require('./math')
math.add()
// 会发现value的值还是1
console.log(math.value)
注意导出的时候还可以使用exports.xxx = value
。
(2)、特点
- 同步加载,由于
CommonJS
是用于服务器端的,服务器端的文件都在本地,所以没什么影响,但是用于浏览器就不行了,文件都是要下载的,采用同步加载的方式就会导致阻塞。 - 输出的是值的拷贝,所以值改变时,不影响之前导出的值。
- 运行时加载,所以无法做到提前分析依赖以及
Tree-Shaking
。 - 所有代码都运行在模块作用域,不会污染全局作用域。
- 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果,要想让模块再次运行,必须清除缓存。
- 模块加载的顺序,按照其在代码中出现的顺序。
AMD
主要用于客户端,异步加载。AMD
和CommonJS
的主要区别在于它是否支持异步模块加载。
// 定义导出模块
// 不依赖其它模块的模块定义
define(function(){
return 模块
})
// 依赖其它模块的模块定义
// 第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。
define(['module1', 'module2'], function(m1, m2){
return 模块
})
// 引入模块
require(['module1', 'module2'], function(m1, m2){
return 模块
})
UMD
它就像是一个工厂,为了同时支持CommonJS
和AMD
的规范,判断谁的规范支持就使用谁的规范,他的外层是一个iife
。并且支持直接在前端用<script src="lib.umd.js"></script>
的方式加载。
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['b'], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require('b'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.b);
}
}(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 {}
}));
ESM
ES6
模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
浏览器直接通过<script type="module">
即可使用该写法。NodeJS
可以通过mjs
后缀或者在package.json
添加"type": "module"
来使用。
(1)、基本语法
// 定义模块 math.js
var basicNum = 0;
var add = function (a, b) {
return a + b;
};
export { basicNum, add };
// 引用模块
import { basicNum, add } from './math';
function test(ele) {
ele.textContent = add(99 + basicNum);
}
(2)、特点
- 完全替代
CJS
和AMD
,淘汰了UMD
,命名空间等规范。 - 静态化,编译时加载,使得页面加载速度快。
-
ES6
模块的运行机制与CommonJS
不一样,它遇到模块加载命令import
时,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值。 -
import
命令具有提升效果,因为是编译时加载,所以会比其他代码都先执行。 -
ES6
模块输出的是值的引用,值改变时,原本输出的值也会变化。
参考文章
1、前端模块化详解(完整版)
2、前端模块化一——规范详述
3、前端模块化iife、CJS、AMD、UMD、ESM的区别
4、打包umd_cjs, umd, esm or iife?