前端模块化方案 (IIFE CommonJS CMD AMD UMD ES6模块化)
背景
Web应用程序变得越来越复杂,前端代码也变得越来越复杂。为了使代码更加可维护,可重用和可扩展,前端模块化已成为现代Web开发的必要条件之一。在本文中,我们将讨论常见的前端模块化方案。
什么是前端模块化?
前端模块化是将一个大型的应用程序拆分成多个小的独立的模块,每个模块都有自己的作用域和依赖关系。每个模块都有自己的职责,可以被独立地开发,测试和维护。模块化的目的是提高代码的可重用性,减少代码冗余,提高代码质量和可维护性。
前端模块化的好处
- 代码可重用性:模块化使得代码可以被独立地开发和测试,减少代码的冗余,提高代码的可重用性。
- 提高代码质量:模块化使得代码可以被分解成更小的部分,每个部分都有自己的职责,可以更容易地进行单元测试和集成测试,提高代码的质量。
- 提高可维护性:模块化使得代码的职责清晰明确,使得代码更容易维护和更新。
- 更好的团队合作:模块化使得团队成员可以独立地开发和测试不同的模块,使得团队合作更加高效和协作。
前端模块化方案有哪些
1. IIFE
IIFE(Immediately Invoked Function Expression):IIFE 是一种 JavaScript 模式,通过使用立即执行的函数表达式,将代码封装在一个独立的作用域中,从而避免全局命名空间的污染。IIFE 的使用通常是在前端的开发中,特别是在使用 jQuery 等库时。在 IIFE 中,定义的变量和函数只能在该函数作用域内访问,不会污染全局作用域。例如:
(function() {
var message = "Hello, World!"; alert(message);
})();
以上代码将在定义时立即执行,而不需要等到整个文档加载完毕,将变量 message 定义在函数内部,从而避免了全局污染,但不太适合大型应用程序。
2. CommonJS
CommonJS 是一种用于服务器端 JavaScript 的模块化规范,NodeJS就是CommonJS规范的实现。CommonJS 模块系统允许开发者使用 require
函数来加载模块,并使用exports
对象来导出模块。例如
// math.js
exports.add = function(a, b) {
return a + b;
};
// app.js
const math = require('./math');
console.log(math.add(1, 2)); // 3
CommonJS 规范是同步加载模块的,因此当应用程序启动时,所有模块都会被立即加载,并返回导出的对象,加载完成前程序会阻塞,直到该模块被完全加载完毕。如果模块之间存在依赖关系,则必须按正确的顺序加载它们,否则会导致错误。此外,由于 CommonJS 规范主要是为 Node.js 环境设计的,因此常用在node webpack中,在浏览器中使用 CommonJS 可能需要使用一些工具来转换代码。
浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量:module、exports、require、global,只要能够提供这四个变量,浏览器就能加载 CommonJS 模块,常用的 CommonJS 格式转换的工具有Browserify
2. AMD
AMD(Asynchronous Module Definition):AMD 是一种异步的模块定义规范,主要用于浏览器端的 JavaScript 应用程序中。AMD 的最初实现是 RequireJS,它通过异步加载模块,避免了浏览器在加载 JavaScript 文件时出现阻塞的情况。AMD 的模块定义语法如下:
// math.js
define(function() {
var add = function(x, y) {
return x + y;
};
var subtract = function(x, y) {
return x - y;
};
return { add: add, subtract: subtract }; });
// app.js
require(['math'], function(math) {
console.log(math.add(2, 3)); // 输出 5 console.log(math.subtract(5, 2)); // 输出 3
});
AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行
主要有两个Javascript库实现了AMD规范:require.js和curl.js。
3. CMD
CMD(Common Module Definition):是由国内的前端模块加载器 seajs 发布的,seaJS是一个CMD模块加载器。与 AMD 类似,CMD 也支持异步模块加载,但是在模块定义时采用了懒加载方式,即不会在模块定义时立即执行模块代码。CMD 的模块定义语法如下:
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.test(); ...
//软依赖 if (status) {
var b = requie('./b'); b.test();
}
});
define
是一个全局函数,用于定义一个模块。function
是一个函数,接受三个参数:require
、exports
和module
。require
是一个函数,用于加载其他模块。在模块代码中使用时,可以通过require(moduleName)
来加载指定的模块。加载的模块会被缓存,因此,如果多次调用require
加载同一个模块,实际上只会加载一次。exports
是一个对象,用于定义模块的导出接口。在模块代码中使用时,可以将需要导出的接口添加到exports
对象上,例如:exports.myFunc = function() {...}
。module
是一个对象,用于获取当前模块的相关信息。在模块代码中使用时,可以通过module.id
来获取模块的标识符,例如:console.log(module.id)
。
CMD与AMD的区别:
- 对于依赖的模块,AMD是提前执行,CMD是延迟执行。 (不过RequireJS从2.0开始,也改成可以延迟执行)
- AMD推崇依赖前置,CMD推崇依赖就近。
4. UMD
UMD(Universal Module Definition):UMD 是一种通用的模块定义规范,支持异步和同步加载模块,旨在提供一种适用于不同环境(浏览器、Node.js 等)的模块加载方案。UMD包装器将代码包装在一个IIFE中,并根据环境来判断使用AMD或CommonJS规范加载模块。通常通过检测全局对象(如 window、global)来判断当前环境,从而采用不同的模块定义方式。UMD 的模块定义语法如下:
(function(root, factory) {
if (typeof define === "function" && define.amd) {
define(["jquery"], factory);
} else if (typeof exports === "object") {
module.exports = factory(require("jquery"));
} else {
root.myModule = factory(root.jQuery);
}
}(this, function($) { // 模块代码 }));
以上代码通过检测全局对象来确定当前环境,如果是 AMD 规范的环境,通过 define 方法异步加载依赖的模块;如果是 CommonJS 规范的环境,通过 exports 导出模块;否则将模块挂载到全局对象上。 UMD是一种通用的模块化方案,。这意味着它可以在浏览器和Node.js环境中使用。
5. ES6 Module
ES6 模块化是一种原生支持的 JavaScript 模块化方案,通过 import 和 export 关键字实现模块化。ES6 模块化是目前最常用的前端模块化方案,它是原生支持的,不需要引入额外的库,使用方便,但是需要使用工具进行转换才能在现代浏览器中使用。
// module1.js
export const foo = 'hello'
export function bar() {
console.log('world')
}
// module2.js
import { foo, bar } from './module1'
console.log(foo) // hello
bar() // world
ES6 Module 和 CommonJS 模块的区别:
-
- 语法上
CommonJS 使用的是 module.exports = {} 导出一个模块对象,require(‘file_path’) 引入模块对象; ES6使用的是 export 导出指定数据, import 引入具体数据。
-
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
ES6 Modules 的运行机制与 CommonJS 不一样。JS遇到模块加载命令import,就会生成一个
只读引用
。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。因此,原始值变了,import加载的值也会跟着变。 -
3、CommonJS 全量加载整个模块,ES6可以选择性加载模块的部分内容
如何选择前端模块化方案
选择前端模块化方案需要考虑多方面因素,包括应用场景、团队技术栈和个人偏好等:
- 应用场景:不同的应用场景需要选择不同的模块化方案,例如只在浏览器中运行的应用可以选择 AMD 或 ES6 模块化方案,而需要在服务器端和浏览器端都运行的应用可以选择 CommonJS 或 UMD 方案。
- 团队技术栈:团队成员的技术栈也需要考虑,如果团队成员已经熟悉了某种模块化方案,那么可以选择相应的方案。
- 个人偏好:个人对某种模块化方案的偏好也需要考虑,如果某种方案符合个人习惯,那么可以优先选择。
转载自:https://juejin.cn/post/7213384257530970173