前端模块化方案 (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.jscurl.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是一个函数,接受三个参数:requireexportsmodule
  • 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 模块的区别:

    1. 语法上

    CommonJS 使用的是 module.exports = {} 导出一个模块对象,require(‘file_path’) 引入模块对象;
    ES6使用的是 export 导出指定数据, import 引入具体数据。

    1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用

    CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

    ES6 Modules 的运行机制与 CommonJS 不一样。JS遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。因此,原始值变了,import加载的值也会跟着变。

  • 3、CommonJS 全量加载整个模块,ES6可以选择性加载模块的部分内容

如何选择前端模块化方案

选择前端模块化方案需要考虑多方面因素,包括应用场景、团队技术栈和个人偏好等:

  • 应用场景:不同的应用场景需要选择不同的模块化方案,例如只在浏览器中运行的应用可以选择 AMD 或 ES6 模块化方案,而需要在服务器端和浏览器端都运行的应用可以选择 CommonJS 或 UMD 方案。
  • 团队技术栈:团队成员的技术栈也需要考虑,如果团队成员已经熟悉了某种模块化方案,那么可以选择相应的方案。
  • 个人偏好:个人对某种模块化方案的偏好也需要考虑,如果某种方案符合个人习惯,那么可以优先选择。

原文链接:https://juejin.cn/post/7213384257530970173 作者:Eden的前端笔记

(1)
上一篇 2023年3月24日 下午7:10
下一篇 2023年3月25日 上午11:00

相关推荐

发表回复

登录后才能评论