前端必知之:前端模块化的CommonJS规范和ES Module规范详解

简介: 【8月更文挑战第1天】

为什么要有模块化?

在早期JavaScript的开发当中,在没有模块化的情况下。写法是这样的:

  <script src="./index.js"></script>
  <script src="./home.js"></script>
  <script src="./user.js"></script>

这种写法很容易存在全局污染和依赖管理混乱问题。在多人开发前端应用的情况下问题更加明显。
命名混乱、代码组织性低、可维护性差、可重用性低等问题暴露的更加明显。

例如:

命名冲突

在没有模块化的情况下,所有的函数和变量都定义在全局作用域中。这意味着如果不小心命名冲突,不同部分的代码可能会意外地互相影响,导致难以察觉的 bug或不可预见的行为。

// 文件 1
function calculateTotal(price, quantity) {
   
   
  return price * quantity;
}
// 文件 2
function calculateTotal(price, taxRate) {
   
   
  return price * (1 + taxRate);
}

如果这两个文件都在全局作用域中定义,且被同一个 HTML 文件引用,那么 calculateTotal 函数会产生冲突,调用时可能会得到不正确的结果。

全局污染

在没有模块化的情况下,所有的变量和函数都被添加到全局命名空间中。这可能导致变量名重复、不必要的全局变量增多,从而增加了代码的复杂性和维护难度。

// 文件 1
var username = 'Alice';

function greetUser() {
   
   
  console.log('Hello, ' + username + '!');
}
// 文件 2
var username = 'Bob';

function displayUsername() {
   
   
  console.log('Current user: ' + username);
}

如果这两个文件都在同一个页面中执行,它们共享同一个全局命名空间,可能会造成 username 被覆盖,从而导致 greetUserdisplayUsername 函数不再使用预期的 username 值。

难以管理和维护

没有模块化的代码通常难以分离、重用和测试。整体项目结构可能变得混乱,不同功能之间的依赖关系也不明确,增加了代码的复杂性和理解难度,特别是在大型项目中。

示例:

// 文件 1
function calculateTotal(price, quantity) {
   
   
  return price * quantity;
}

function formatCurrency(amount) {
   
   
  return '$' + amount.toFixed(2);
}
// 文件 2
function calculateTax(total, taxRate) {
   
   
  return total * taxRate;
}

function formatCurrency(amount) {
   
   
  return '¥' + amount.toFixed(2);
}

在没有模块化的情况下,两个文件都在全局作用域中定义 formatCurrency 函数,如果它们都被加载到同一个页面中,会出现函数覆盖和不一致的行为。

模块的概念及使用原因

使用模块化工具(如 ES6 的模块化或 CommonJS)可以有效地解决上述问题。模块化工具允许我们将代码组织成独立的、封闭的模块,每个模块有自己的作用域,只暴露需要的接口,从而避免命名冲突、全局污染和代码管理上的困难。

模块化开发是我们开发当中用于组织和管理代码的方法,它的目的是将复杂的应用程序去拆分为更小和更好管理的模块单元,从而提高代码的复用性和可维护性。

JavaScript中,模块化是一种将代码分割成独立、可复用的部分的方法。ES6引入了ES Modules(ESM)作为原生的模块系统,而CommonJSNode.js中使用的模块系统。

ES模块CommonJS模块化方案都被广泛使用。以下是两者的详细解释和示例代码。

ES Module

ES Module 是 ECMAScript 6 引入的官方模块化方案,它具有以下特点:

  • 使用 importexport 关键字定义模块。
  • 支持静态导入(在编译时解析)和动态导入(在运行时异步加载)。
  • 原生支持异步加载,使用 import() 函数。

示例 ES Module:

// math.js
export function add(a, b) {
   
   
  return a + b;
}

export function subtract(a, b) {
   
   
  return a - b;
}
// app.js
import {
   
    add, subtract } from './math';

console.log(add(5, 3)); // 输出 8
console.log(subtract(5, 3)); // 输出 2

自定义模块原则

自定义模块在 Node.js 环境中的查找原则如下:

  1. 在当前目录下的 node_modules 目录查找
  2. 向上级目录逐级查找,直至根目录
  3. 查找 package.json 中的 main 属性指定的入口文件
  4. 默认文件名查找(index.js、index.json、index.node)

以下是一个简单的自定义模块查找流程示意图:
image.png

在这个示例中,如果要加载模块A,Node.js 会首先在当前目录下的 node_modules 中找到对应的模块文件或者 package.json 中指定的入口文件。如果未找到,则向上逐级查找,直至根目录。

主要特点

  • ES Module 是现代 JavaScript 的官方模块化方案,具有静态导入和动态导入的能力,适合在浏览器和 Node.js 环境中使用。
  • 自定义模块原则查找流程 确保了在 Node.js 中引入模块时的灵活性和便捷性,无需手动指定路径。

这些特性和原则使得 JavaScript 开发中的模块化更加高效和易于管理。

CommonJS

ES Module中,使用exportimport关键词来导出或导入模块。在CommonJS中,使用module.exportsrequire()来导出模块和引入模块。两者都是JavaScript模块化的方式,但是主要应用环境和语法有所区别。在浏览器端,可以通过Webpack等工具将CommonJS代码转换为ES Module代码以便在浏览器中使用。在Node.js环境中,直接使用CommonJS模块系统即可。

// math.js
function sum(a, b) {
   
   
  return a + b;
}

function multiply(a, b) {
   
   
  return a * b;
}

module.exports = {
   
    sum, multiply };

// main.js
const {
   
    sum, multiply } = require('./math.js');

console.log(sum(1, 2)); // 输出:3
console.log(multiply(1, 2)); // 输出:2

两者的主要区别在于导出和导入的语法以及在不同环境下的加载机制。ES模块采用importexport语法,而CommonJS模块采用requiremodule.exports。此外,ES模块是静态的,需要构建工具转换后才能在不支持ES模块的环境中运行,而CommonJS模块可以直接在Node.js等环境中运行。

主要特点和使用方法:

  1. 模块定义和导出:

    • 使用 module.exports 导出模块的功能或变量。
    • 使用 require() 函数引入其他模块的功能或变量。
  2. 模块加载的同步性:

    • CommonJS 加载模块是同步进行的,即 require() 函数会阻塞代码执行直到模块加载完成。这种同步加载模式在服务器端常用,但在浏览器端可能会影响性能。
  3. 模块的缓存:

    • require() 函数加载的模块会被缓存,避免多次加载相同模块时造成的性能损失和状态问题。
  4. 动态加载:

    • require() 的参数可以是动态计算的表达式,允许根据需要动态加载模块,这在某些场景下非常有用。

    示例:

    const moduleName = './math';
    const math = require(moduleName);
    
  5. 适用范围:

    • CommonJS 最初设计用于 Node.js 等服务器端 JavaScript 环境,支持在同步 I/O 操作下方便地组织和加载模块。在浏览器端,可以使用工具如 Browserify 或 Webpack 将 CommonJS 格式的模块转换成适合浏览器运行的代码。

优点:

  • 简单直观: 使用 module.exportsrequire() 的方式非常直观,易于理解和使用。
  • 适用于服务器端: 在服务器端 JavaScript 开发中得到广泛应用,因其简单性和实用性。

缺点:

  • 同步加载: 阻塞式加载模块可能在大型应用中导致性能问题,特别是在需要异步加载的场景下。
  • 浏览器兼容性问题: 浏览器环境并不原生支持 CommonJS,需要使用工具转换或者使用 ECMAScript 模块化规范(ES6 模块)。

自定义模块查找流程

当处理自定义模块时,查找流程通常遵循以下步骤:

  1. 在当前目录下的 node_modules 目录查找

    • 首先,Node.js 尝试在当前执行脚本所在的目录中的 node_modules 文件夹中查找需要引入的模块。
  2. 向上级目录逐级查找

    • 如果在当前目录下未找到,Node.js 将向上级目录逐级查找,直到根目录。每一级目录都会检查其下的 node_modules 文件夹。
  3. 查找 package.json 中的 main 属性

    • 如果找到模块所在的文件夹,并且该文件夹中包含一个 package.json 文件,Node.js 将查看 package.json 文件中的 main 属性指定的入口文件。
  4. 默认文件名查找

    • 如果没有找到 package.json 或者 main 属性未定义,Node.js 将默认使用以下文件名来查找入口文件:
      • index.js
      • index.json
      • index.node
  5. 递归向上直至根目录

    • 如果在当前执行脚本的根目录下的 node_modules 文件夹中仍未找到,Node.js 将放弃查找并抛出一个错误。

这种查找模块的方式保证了在 Node.js 环境中可以方便地引入自定义模块,而不需要显式指定绝对路径。以下是一个简单的流程图示例:

image.png

您好,我是肥晨。
欢迎关注我获取前端学习资源,日常分享技术变革,生存法则;行业内幕,洞察先机。

目录
相关文章
|
1月前
|
前端开发 JavaScript
为什么要前端模块化?带你了解前端模块化
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。自学前端2年半,现正向全栈发展。如果你觉得我的文章有帮助,欢迎关注我,将持续更新更多优质内容!🎉🎉🎉
28 1
为什么要前端模块化?带你了解前端模块化
|
2月前
|
前端开发 测试技术
如何从零到一建立前端规范
【10月更文挑战第6天】
73 2
|
7月前
|
前端开发 JavaScript API
前端代码书写规范
前端代码规范提升项目可维护性和团队协作效率。关注项目命名清晰简洁、一致性,组件命名使用驼峰式且具描述性。JS遵循4空格缩进,分号结束语句,CSS按逻辑排序,HTML注重语义化。注释要功能性、文档化且简洁。遵循规范能减少错误,增强团队沟通。
303 3
|
4月前
|
前端开发 JavaScript 开发工具
前端规范
前端规范
|
3月前
|
监控 前端开发 开发者
前端代码规范 - 日志打印规范
前端代码规范 - 日志打印规范
|
4月前
|
前端开发
前端代码书写规范
【8月更文挑战第15天】前端代码书写规范
89 0
|
6月前
|
缓存 JavaScript 前端开发
前端小白也能懂:ES模块和CommonJS的那些事
【6月更文挑战第1天】在JavaScript的世界中,模块化是构建大型应用的关键。ES模块(ESM)和CommonJS是两种主流的模块系统,它们各自有着不同的特性和使用场景。你了解它们的区别吗?
368 2
|
2月前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
183 2
|
2月前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
49 0
|
2月前
|
人工智能 自然语言处理 运维
前端大模型应用笔记(一):两个指令反过来说大模型就理解不了啦?或许该让第三者插足啦 -通过引入中间LLM预处理用户输入以提高多任务处理能力
本文探讨了在多任务处理场景下,自然语言指令解析的困境及解决方案。通过增加一个LLM解析层,将复杂的指令拆解为多个明确的步骤,明确操作类型与对象识别,处理任务依赖关系,并将自然语言转化为具体的工具命令,从而提高指令解析的准确性和执行效率。
下一篇
DataWorks