(JavaScript)ES6模块与CommonJS模块的异同

前言

ES6模块和CommonJS模块是在JavaScript中用于模块化编程的两种不同标准,本文将用生动形象的例子讲解有关两者的异同

模块化编程简介

模块化编程是一种软件设计和开发方法,旨在将一个大型软件系统分解为小块可维护的模块或组件,这些模块可以独立开发、测试和维护。每个模块通常负责特定功能或任务,它们之间的接口清晰定义,以便实现代码的重用和降低复杂性。模块化编程有以下重要概念和优点:

  1. 封装和抽象:每个模块隐藏了内部实现的细节,只暴露必要的接口,这有助于减少代码的复杂性和提高可维护性。模块的用户只需关心如何使用模块,而不需要了解其内部实现。
  2. 代码重用:模块可以在不同的项目中重复使用,从而提高开发效率。开发人员可以构建和测试单独的模块,然后将它们组合在一起以创建更大的应用程序。
  3. 解耦:模块化编程有助于降低模块之间的耦合度,即模块之间的依赖关系。这使得更容易进行单独的模块测试和维护。
  4. 可维护性:由于每个模块都是相对独立的,因此更容易定位和修复错误。模块化设计有助于减小代码库的规模,使其更易于维护和扩展。
  5. 协作:不同开发人员可以并行开发不同的模块,而不会过多地干扰彼此的工作。这有助于多人团队协作开发大型项目。

在JavaScript中,模块化编程可以通过不同的标准和工具来实现,包括CommonJS、ES6模块、AMD(异步模块定义)等。这些标准提供了不同的语法和机制,但都有助于将JavaScript代码分解为可重用的模块,从而改善了JavaScript应用程序的结构和可维护性。

语法上的差异

ES6模块像一本现代英语教材,而CommonJS模块就像一本古老的字典 ,ES6模块使用了更现代的语法,使用importexport关键字来定义和导出模块。这种语法更加清晰和简洁。相比之下,CommonJS模块使用requiremodule.exports来导入和导出模块,它们的语法更加冗长和古老。

ES6模块:

  • ES6模块采用现代的、更易读的语法。
  • 使用import关键字来导入其他模块中的内容,例如:import { someFunction } from './myModule';
  • 使用export关键字来导出模块中的函数、变量或类,例如:export function someFunction() { /* ... */ }
  • ES6模块支持默认导出和命名导出,可以在同一个模块中混合使用。

CommonJS模块:

  • CommonJS模块采用相对古老的语法。
  • 使用require函数来导入其他模块中的内容,例如:const someFunction = require('./myModule');
  • 使用module.exports对象来导出模块中的内容,例如:module.exports = { someFunction }
  • CommonJS模块通常不直接支持默认导出,但可以通过module.exports设置一个默认导出。

综上所述,ES6模块的语法更现代、更直观,因为它使用importexport关键字,这使得模块导入和导出更清晰。相反,CommonJS模块的语法更加古老,使用requiremodule.exports,这可能显得冗长和不够直观。这些语法差异是由于它们的不同历史和设计目标,ES6模块被设计成更加适合浏览器环境,而CommonJS模块则是为Node.js服务器端环境而创建的。随着时间的推移,ES6模块在JavaScript生态系统中逐渐取代了CommonJS模块,特别是在浏览器端。

动态与静态的不同

ES6模块就像建筑蓝图,而CommonJS模块就像建筑现场。 ES6模块在编译时静态分析,所有导入和导出都在模块加载前确定,这使得代码更加可预测和优化。CommonJS模块在运行时加载,这意味着模块的依赖关系可能在运行时发生变化,不太适合浏览器环境,但在服务器端很有用

ES6模块(静态):

  • ES6模块的依赖关系在编译时 进行静态分析,这意味着在模块加载之前,所有导入和导出关系都已明确定义
  • 在编译时,JavaScript引擎可以确定模块之间的依赖关系,这使得它可以进行更好的优化,例如剔除未使用的导入,从而减小最终构建的文件大小。
  • 静态分析还使代码更加可预测,开发者能够清楚地了解哪些模块被导入,哪些被导出,而不会有意外的行为。

CommonJS模块(动态):

  • CommonJS模块在运行时动态加载,依赖关系只有在代码执行时才会被解析
  • 运行时加载的模块依赖关系可能导致一些问题,特别是在浏览器环境中。例如,如果模块A依赖于模块B,但模块B的加载时间较长,可能会导致模块A在模块B加载之前访问不到它的导出,从而引发错误。
  • 动态加载对于服务器端JavaScript(如Node.js)通常是合适的,因为服务器环境通常是单线程的 ,不会出现上述问题,而且允许延迟加载模块以提高性能

因此,可以将ES6模块视为建筑蓝图,因为它们在编译时已经明确定义了模块之间的依赖关系,而CommonJS模块则类似于建筑现场,因为它们的依赖关系是在运行时动态加载的,这可能会导致一些不确定性和性能问题,尤其在浏览器环境中。这也是为什么ES6模块在现代前端开发中更常用的原因之一。

同步与异步的不同

ES6模块就像按顺序排队购物,而CommonJS模块就像同时抢购。ES6模块是同步加载的,模块的导入和导出是按顺序执行的,不会出现竞态条件。CommonJS模块是异步加载的,模块的加载是并行的,这可能导致竞态条件和不确定性。

ES6模块(同步):

  • ES6模块是同步加载的,这意味着模块的导入和导出是按顺序执行 的,且不会出现竞态条件
  • 当一个模块引用另一个模块时,它会等待被引用的模块完全加载并执行后才继续执行 。这确保了模块之间的依赖关系被满足,代码执行的顺序是可控的

CommonJS模块(异步):

  • CommonJS模块在运行时异步加载,模块的加载是并行的,这可能导致竞态条件和不确定性
  • 当一个模块引用另一个模块时,它会开始加载后立即继续执行,而不等待被引用的模块加载完成。这可能会导致在使用被引用模块的导出之前,尚未加载完成,从而引发错误或不确定的行为。

这种同步与异步的差异是由于两种模块系统的设计目标不同。ES6模块的同步性有助于浏览器在构建时进行更好的优化,而CommonJS模块的异步性有助于服务器端JavaScript的并行加载和性能优化。因此,选择哪种模块系统通常取决于应用程序的需求和运行环境。

ESM的默认导出

ES6模块就像一位有名字的演员,而CommonJS模块就像一位无名的配角。ES6模块支持默认导出,这意味着一个模块可以有一个主要导出,而CommonJS模块通常没有默认导出,每个导出都需要命名。

ES6模块的默认导出:

  • 在ES6模块中,一个模块可以有一个主要的默认导出,这类似于一位有名字的主演。
  • 默认导出是一个特殊的导出方式,用于导出模块中的主要功能、类、或对象。默认导出通常用于模块的核心功能,使其更容易引用和识别。
  • 在导入时,你可以为默认导出起一个名字,但通常不需要使用花括号来指定名称。
js 复制代码
// es6Module.js
const mainFunction = () => {
  // 主要功能的实现
};

export default mainFunction;

然后,你可以这样导入默认导出:

js 复制代码
import myActor from './es6Module';

CommonJS模块的导出:

  • 在CommonJS模块中,通常没有默认导出的概念,而是通过命名导出来实现模块中的功能。
  • 每个导出都需要命名,你需要使用相应的名称来引用导出的功能或对象。
  • 在导入时,你需要使用require和指定的名称来引用模块的导出。
js 复制代码
// commonjsModule.js
const mainFunction = () => {
  // 主要功能的实现
};

exports.mainFunction = mainFunction;

然后,你可以这样导入CommonJS模块的导出:

js 复制代码
const myActor = require('./commonjsModule');
const mainFunction = myActor.mainFunction;

总之,ES6模块支持默认导出,这使得模块的主要功能更容易被引用,就像一位有名字的主演。而CommonJS模块通常没有默认导出,每个导出都需要命名,就像一位无名的配角。这种默认导出的特性在ES6模块中提供了更直观的导入语法和更清晰的模块结构。

CommonJS模块的浅拷贝:

  • 在CommonJS模块中,当一个模块导出一个对象(例如一个JavaScript对象、数组、函数等),其他模块导入它时,实际上获得了一个对该对象的浅拷贝(shallow copy)。
  • 这意味着其他模块获得的对象与原始导出的对象是相同的引用,因此可以修改该对象的属性或成员,这将反映在所有导入该模块的地方。

ES6模块的引用(类似const):

  • 在ES6模块中,当一个模块导出一个对象,其他模块导入它时,它们获得的是一个对原始对象的只读引用,类似于const变量。
  • 这意味着其他模块无法修改导出模块的对象的引用,因为它们获得的引用是只读的,任何尝试修改对象属性的操作都会失败。

这两种方法的区别在于CommonJS模块的导出是基于浅拷贝的,而ES6模块的导出是基于引用的,只读的。这是因为CommonJS模块系统主要是为服务器端设计的,其中通常需要共享可变状态,而ES6模块系统更注重不变性和预测性,特别适用于浏览器环境和前端开发中的模块化编程。

相同之处

CommonJS 和 ES6 Module 都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变。

关于一些会混淆的点的解释

关于导出模块的对象的修改

ES6模块 中,其他模块导入它后,它们获得的是对原始对象的只读引用 ,类似于const变量。这意味着其他模块可以修改导出模块的对象的属性值,但不能重新分配一个全新的对象给导出模块的标识符。

js 复制代码
// es6Module.js
const sharedObject = { value: 42 };
export { sharedObject };

// anotherES6Module.js
import { sharedObject } from './es6Module';
sharedObject.value = 100; // 修改属性值是允许的

// anotherES6Module.js
import { sharedObject } from './es6Module';
sharedObject = { value: 200 }; // 不能重新分配 sharedObject

在CommonJS模块中,当一个模块导出一个对象,其他模块导入它后,它们获得的是对原始对象的引用,而不是只读引用。这意味着其他模块可以修改导出模块的对象的属性值,也可以重新分配一个全新的对象给导出模块的标识符。因此,在CommonJS模块中,你可以对导出模块的对象的属性值进行更改,也可以更改它的引用。

js 复制代码
// commonjsModule.js
const sharedObject = { value: 42 };
exports.sharedObject = sharedObject;

// anotherCommonJSModule.js
const sharedObject = require('./commonjsModule');
sharedObject.sharedObject.value = 100; // 修改属性值是允许的
sharedObject.sharedObject = { newValue: 200 }; // 重新分配新对象是允许的

在CommonJS模块系统中,模块之间通常共享可变状态,因此其他模块可以更改导出模块的对象的属性值并重新分配新对象。这与ES6模块有所不同,后者更强调不变性和只读性。

关于运行与加载

在服务器端 JavaScript 环境(如 Node.js)通常是单线程的,这意味着 JavaScript 代码在运行时是单线程执行的,即一次只能执行一个操作。这是因为 Node.js 采用了事件驱动的单线程执行模型,它使用事件循环来处理异步操作,但单线程执行 JavaScript 代码。

然而,模块加载的过程可以是并行的。在 Node.js 中,模块加载是基于 CommonJS 模块系统,它允许并行加载模块,特别是在多核处理器的情况下。这意味着多个模块可以同时被加载,而不必等待前一个模块加载完成。这种并行加载是为了提高性能,确保模块加载不会成为性能瓶颈。

在浏览器端,情况可能会更复杂,因为浏览器中的 JavaScript 是运行在单个主线程中的,通常不会有多线程并行执行 JavaScript 代码。但浏览器可以通过异步加载脚本来实现类似的模块并行加载,以避免页面阻塞。

总之,在服务器端 JavaScript 环境中,JavaScript 代码运行时是单线程的,但模块加载可以是并行的。在浏览器端,JavaScript 代码运行时也是单线程的,但通过异步加载和Web Workers等技术,可以实现一定程度的并行加载和执行。

相关推荐
晓Ming_4 分钟前
SweetAlert2 - 漂亮可定制的 JavaScript 弹窗
前端·javascript
大鱼前端3 小时前
2025年,AI时代下的前端职业思考
前端
勉灬之3 小时前
封装上传组件,提供各种校验、显示预览、排序等功能
开发语言·前端·javascript
outstanding木槿5 小时前
react中实现拖拽排序
前端·javascript·react.js
ordinary905 小时前
vue.js scoped样式冲突
前端·vue.js
我要学编程(ಥ_ಥ)6 小时前
速通前端篇——JavaScript
开发语言·前端·javascript
大强的博客7 小时前
《Vue3实战教程》19:Vue3组件 v-model
前端·javascript·vue.js
塔塔开!.7 小时前
element ui 组件 时间选择器出现转换问题的解决办法
前端·javascript·vue.js
胡桃夹夹子8 小时前
前端,npm install安装依赖卡在sill idealTree buildDeps(设置淘宝依赖)
前端·npm·node.js
xing.yu.CTF8 小时前
HTML基础到精通笔记
前端·笔记·html