【Node.js】Common JS 和 ES Module 对于导出值的探究

CommonJS

在 CommonJS 模块中,模块的输出是对象引用的拷贝。这意味着,如果导出的对象在模块内发生了修改,其他地方通过 require 导入的内容也会反映这些更改。

以下是详细说明:

  1. 模块缓存

    • CommonJS 模块在首次被 require 时,模块的代码会执行,导出对象会被创建并存储在缓存中。
    • 后续对该模块的 require 调用将返回缓存中的导出对象。
  2. 引用行为

    • 如果模块导出的是一个对象或数组,外部对该导入对象的修改会影响其他地方导入的对象,因为它们引用的是同一个内存地址。

示例:

moduleA.js:

const data = { count: 1 };function increment() {data.count++;
}module.exports = { data, increment };

main.js:

const moduleA = require('./moduleA');
console.log(moduleA.data.count); // 输出: 1moduleA.increment();
console.log(moduleA.data.count); // 输出: 2const anotherImport = require('./moduleA');
console.log(anotherImport.data.count); // 输出: 2

解释

  • module.exports 是一个对象的引用。
  • require('./moduleA') 返回缓存中相同的引用。
  • 修改 moduleA.data 会反映在所有导入中。

注意:

  • 如果直接导出原始值(如数字、字符串等),这将是一个值的拷贝,因为原始值是不可变的。例如:

moduleB.js:

let count = 1;function increment() {count++;
}module.exports = { count, increment };

main.js:

const moduleB = require('./moduleB');
console.log(moduleB.count); // 输出: 1moduleB.increment();
console.log(moduleB.count); // 输出: 1(值的拷贝,不会自动更新)

总结

  • CommonJS 模块导出的对象是引用的拷贝。
  • 如果导出的是原始值,则导入后是值的拷贝,不会随着模块内的变化自动更新。

ES Module

在 ES Modules(ESM)中,模块的导出是通过引用的方式共享的。这意味着:

  1. 导出的值是动态绑定的

    • 导入的内容会实时反映模块中导出的最新状态(如果导出的值是对象或可变变量)。
    • ES Modules 会确保模块导出和导入的值保持同步。
  2. 与 CommonJS 的区别

    • CommonJS 在模块加载时会将导出的内容生成一次并缓存(静态快照,原始值不会同步更新)。
    • ESM 的导出是动态绑定的,即使导出的是原始值,更新模块内的值后,导入的地方也会反映出变化。

1. 对象引用
如果导出的是对象,导入的地方和模块内部共享同一个引用。

moduleA.mjs:

export const data = { count: 1 };export function increment() {data.count++;
}

main.mjs:

import { data, increment } from './moduleA.mjs';console.log(data.count); // 输出: 1increment();
console.log(data.count); // 输出: 2

解释

  • data 是通过引用共享的,模块内部修改会直接反映到导入的地方。

2. 原始值(动态绑定)
如果导出的是原始值(如数字、字符串),模块内部更新的值会同步到导入的地方。

moduleB.mjs:

export let count = 1;export function increment() {count++;
}

main.mjs:

import { count, increment } from './moduleB.mjs';console.log(count); // 输出: 1increment();
console.log(count); // 输出: 2

解释

  • count 是动态绑定的,尽管它是原始值,模块内部的更新会反映到导入的地方。

ESM 的特点:

  1. 静态结构

    • ES Modules 是在编译时解析的,importexport 必须位于顶层。
    • 无法在运行时动态导入或导出(但可以使用 import() 动态加载模块)。
  2. 实时绑定

    • ESM 的导出是动态绑定的,导入的值会随模块内的更新而更新。
  3. 单例模式

    • ES Modules 是单例的,多次导入同一个模块时,得到的都是同一个模块实例。

对比总结

特性CommonJSES Modules
导出机制值的拷贝(原始值),引用拷贝(对象)动态绑定
导入时机运行时(动态加载)编译时(静态加载)
更新机制静态快照实时更新
模块缓存
用法require()import/export

因此,ESM 更适合处理动态绑定和模块间实时同步的场景。

其他区别

1. 模块加载时机

  • CommonJS:模块加载是 同步 的,模块加载时会立即执行,并返回模块的导出内容。因此,require 语句会阻塞,直到模块完全加载并执行完成。
  • ESM:模块加载是 异步 的,在浏览器和 Node.js 中,ESM 模块在加载时不会阻塞,尤其是在浏览器环境下,模块加载会是异步的。

2. 文件扩展名

  • CommonJS:支持 .js, .json, 和 .node 扩展名的文件。模块导入时,如果没有扩展名,Node.js 会自动尝试加载 .js.json.node 文件。
  • ESM:在 Node.js 环境下,ESM 强制要求指定扩展名,除非使用 .js 文件且该文件是 ES Module(在 package.json 中指定 "type": "module")。这意味着必须明确指定扩展名,如 import x from './module.mjs'import x from './module.js'

3. 模块的加载方式

  • CommonJS:在执行 require 时,模块被加载并执行一次,之后会缓存模块的导出内容。模块执行的顺序是按调用 require 的顺序同步执行。
  • ESM:ESM 模块的加载是 按需 加载的,ESM 模块会被动态解析并可能会被异步加载。ESM 模块会被延迟执行,且加载过程支持静态分析,允许更强大的工具进行优化和死代码删除。

4. importexport 语法

  • CommonJS

    • 导入:const foo = require('foo');
    • 导出:module.exports = foo;exports.foo = foo;
  • ESM

    • 导入:import foo from 'foo';import { bar } from 'foo';
    • 导出:export default foo;export { foo };

    ES Modules 使用静态的 importexport 语法,在编译时就可以解析出模块的依赖关系,而 CommonJS 使用动态的 requiremodule.exports,只能在运行时解析。

5. this 的行为

  • CommonJS:在模块中,this 默认指向 module.exports。这意味着如果你没有显式地导出模块,this 将指向一个空对象({})。

    // CommonJS
    console.log(this); // 输出: {}
    this.foo = 'bar';
    console.log(module.exports); // 输出: { foo: 'bar' }
    
  • ESMthis 在 ESM 模块中并不会指向 exportsmodule.exports,它指向 undefined。在 ESM 中,exportimport 被静态解析,this 并不是用于模块导出的机制。

    // ESM
    console.log(this); // 输出: undefined
    

6. 异步性

  • CommonJS:模块加载是同步的,因此适合于服务器端的环境,尤其是在 Node.js 中,文件读取通常是同步的。

  • ESM:ESM 支持异步加载模块,尤其是在浏览器中,ESM 是按需加载的,可以延迟模块的加载,提高性能。对于 Node.js,也提供了 import() 异步加载的能力。

    // 使用动态导入
    import('./module.js').then(module => {console.log(module);
    });
    

7. 循环依赖处理

  • CommonJS:处理循环依赖时,CommonJS 会在模块第一次加载时返回模块的当前状态,后续 require 调用会返回这个已经部分执行的模块。这意味着如果一个模块还未完全执行,其他模块就可以访问到它的部分导出。

  • ESM:ESM 通过引入 动态绑定 的方式来处理循环依赖。如果模块 A 导入模块 B,而模块 B 又导入模块 A,模块 A 和 B 中的导出会是动态的绑定关系,始终反映最新的值。

8. 命名空间导出

  • CommonJS:导出的内容是一个对象,可以直接添加到 module.exportsexports 上,这通常意味着你可以在导出时使用任意结构(如对象、数组、函数等)。

    // CommonJS 导出
    module.exports = { foo: 'bar', baz: 'qux' };
    
  • ESM:ESM 使用静态导出和导入的机制,且导入时会自动生成一个“命名空间”对象。这意味着你可以选择性地导入模块的部分功能。

    // ESM 导出
    export const foo = 'bar';
    export const baz = 'qux';
    

9. requireimport 的行为

  • CommonJSrequire 是一个动态函数,可以在任何地方使用,因此可以在条件语句、循环中动态加载模块。

    if (condition) {const module = require('./module');
    }
    
  • ESMimport 是静态的,必须在模块的顶部使用,并且不能在条件语句、循环中动态加载。import 的静态特性使得打包工具(如 Webpack)能够优化模块的加载顺序和去除死代码。

    // ESM 只能在顶部使用
    import { foo } from './module';
    

10. 浏览器支持

  • CommonJS:原生不支持浏览器,通常需要通过打包工具(如 Webpack、Browserify)进行转换。

  • ESM:ESM 从一开始就设计为浏览器支持的标准。现代浏览器原生支持 <script type="module"> 标签,可以直接加载 ESM 模块。Node.js 在 v12+ 也原生支持 ESM。

11. 模块作用域

  • CommonJS:模块是封装在一个函数中,模块内部的变量和函数默认不污染全局作用域。

  • ESM:ESM 的模块作用域也类似,每个模块都有自己的作用域,且不污染全局作用域。ESM 具有更强的静态分析能力和模块系统支持。


总结

特性CommonJSES Modules
加载方式同步加载异步加载
语法require(), module.exportsimport, export
模块缓存模块首次加载时缓存模块首次加载时缓存
模块作用域模块内有自己的作用域,this 默认指向 module.exports模块内有自己的作用域,this 指向 undefined
循环依赖部分执行的模块对象动态绑定,解决循环依赖
扩展名支持 .js, .json, .node强制需要扩展名(如 .js, .mjs
适用场景主要用于服务器端,Node.js适用于浏览器和 Node.js,支持异步加载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/892163.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

爬虫学习记录

1.概念 通过编写程序,模拟浏览器上网,然后让其去互联网上抓取数据的过程 通用爬虫:抓取的是一整张页面数据聚焦爬虫:抓取的是页面中的特定局部内容增量式爬虫:监测网站中数据更新的情况,只会抓取网站中最新更新出来的数据 robots.txt协议: 君子协议,网站后面添加robotx.txt…

玩机搞机基本常识-------列举安卓机型一些不常用的adb联机命令

前面分享过很多 常用的adb命令&#xff0c;今天分享一些不经常使用的adb指令。以作备用 1---查看当前手机所有app包名 adb shell pm list package 2--查看当前机型所有apk包安装位置 adb shell pm list package -f 3--- 清除指定应用程序数据【例如清除浏览器应用的数据】 …

【25考研】川大计算机复试情况,重点是啥?怎么准备?

24年进入复试的同学中&#xff0c;有10位同学的复试成绩为0分。具体是个人原因还是校方原因&#xff0c;还尚不明确。但是C哥提醒&#xff0c;一定要认真复习&#xff01;复试完后不要跟任何人讨论有关复试的题目及细节&#xff01; 一、复试内容 四川大学复试内容较多&#xf…

计算机的错误计算(二百零五)

摘要 基于一位读者的问题&#xff0c;提出题目&#xff1a;能用数值计算证明 吗&#xff1f;请选用不同的点&#xff08;即差别大的数&#xff09;与不同的精度。实验表明&#xff0c;大模型理解了题意。但是&#xff0c;其推理能力值得商榷。 例1. 就摘要中问题&#xff0…

回归预测 | MATLAB实GRU多输入单输出回归预测

回归预测 | MATLAB实GRU多输入单输出回归预测 目录 回归预测 | MATLAB实GRU多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 回归预测 | MATLAB实GRU多输入单输出回归预测。使用GRU作为RNN的一种变体来处理时间序列数据。GRU相比传统的RNN有较好的记…

unity学习12:地图相关的一些基础2, 增加layer种草种树

目录 参考学习 1 地图设置 1.1 上次制作的地图&#xff0c;稍微加点地形完善下. 1.2 调整下camera 1.3 摄像机camera的移动速度 1.4 地图属性&#xff0c;terrain settings 1.5 但是&#xff0c;地图看起来像沙漠一样&#xff0c;很单调 2 paint terrain / paint textu…

数据挖掘——数据预处理

数据挖掘——数据预处理 数据预处理数据预处理 ——主要任务数据清洗如何处理丢失的数据如何处理噪声数据如何处理不一致数据 数据集成相关分析相关系数(也成为皮尔逊相关系数)协方差 数据规约降维法&#xff1a;PCA主成分分析降数据——抽样法数据压缩 数据预处理 数据预处理…

Python入门教程 —— 网络编程

1.网络通信概念 简单来说,网络是用物理链路将各个孤立的工作站或主机相连在一起,组成数据链路,从而达到资源共享和通信的目的。 使用网络的目的,就是为了联通多方然后进行通信,即把数据从一方传递给另外一方。 前面的学习编写的程序都是单机的,即不能和其他电脑上的程…

鸿蒙APP之从开发到发布的一点心得

引言&#xff1a; 做鸿蒙开发大概有1年左右时间了&#xff0c;从最开始的看官方文档、看B站视频&#xff0c;到后来成功发布两款个人APP&#xff08;房贷计算极简版、时简时钟 轻喷&#xff0c;谢谢&#xff09;。简单描述一下里边遇到的坑以及一些经历吧。 学习鸿蒙开发 个…

力扣刷题:数组OJ篇(上)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 目录 1.消失的数字&#xff08;1&#xff09;题目描…

linux下多个硬盘划分到同一挂载点

Linux下多个硬盘划分到同一挂载点 需要明确的几个概念 物理卷: 物理卷是物理存储设备&#xff08;如硬盘分区、整个硬盘、RAID 阵列等&#xff09;在逻辑卷管理&#xff08;LVM - Logical Volume Manager&#xff09;系统中的抽象表示。它是构建逻辑卷组的基本单元 假设我们有…

2.STM32F407ZGT6-外部中断

参考&#xff1a; 1.正点原子。 前言&#xff1a; MCU最重要的一个领域–中断。总结下嵌套向量和外部中断的概念。达到&#xff1a; 1.NVIC是什么&#xff0c;了解中断的整体管理理念。 2.中断里面最简单的外部中断&#xff0c;怎么配置处理。 3.使用STM32CubeMX配置外部中断的…

《HeadFirst设计模式》笔记(下)

代理模式 代理要做的就是控制和管理访问。 你的客户对象所做的就像是在做远程方法调用&#xff0c;但其实只是调用本地堆中的“代理”对象上的方法&#xff0c;再由代理处理所有网络通信的低层细节。 Java的RMI提供了客户辅助对象和服务辅助对象&#xff0c;为客户辅助对象…

【学Rust开发CAD】1 环境搭建

文章目录 一、搭建C/C编译环境二、安装Rust三、配置 PATH 环境变量四、验证安装结果五、安装编辑工具 一、搭建C/C编译环境 Rust 的编译工具依赖 C 语言的编译工具&#xff0c;这意味着你的电脑上至少已经存在一个 C 语言的编译环境。如果你使用的是 Linux 系统&#xff0c;往…

【Linux系列】Vim 编辑器中的高效文本编辑技巧:删除操作

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Python爬虫与1688图片搜索API接口:深度解析与显著收益

在电子商务的浩瀚海洋中&#xff0c;数据是驱动业务决策的核心引擎。阿里巴巴旗下的1688平台&#xff0c;作为全球领先的B2B在线市场&#xff0c;不仅汇聚了海量的商品信息&#xff0c;还提供了丰富的API接口&#xff0c;为开发者提供了强大的数据获取工具。本文将深入探讨1688…

IDEA 字符串拼接符号“+”位于下一行的前面,而不是当前行的末尾

效果图 IDEA 默认效果是“历史效果”&#xff0c;经过修改后为“预期效果” 设置方式 在设置中找到Editor > Code Style > Java > Wrapping and Braces > Binary expressions > 勾选 Operation sign on next line 即可实现。具体设置如图。

/src/utils/request.ts:axios 请求封装,适用于需要统一处理请求和响应的场景

文章目录 数据结构解释1. 核心功能2. 代码结构分析请求拦截器响应拦截器 3. 改进建议4. 总结 console.log(Intercepted Response:, JSON.stringify(response));{"data": {"code": 0,"msg": "成功","data": {"id":…

基于FPGA的洗衣机控制器电子定时器

文章目录 功能描述 一、框架 二、verilog代码 控制模块实现 三、视频上板效果展示 功能描述 &#xff08;1&#xff09;定时启动正转20秒暂停10秒反转20秒暂 停10秒&#xff0c;定时未到回到“正转20秒暂停10秒……”&#xff0c;定时到则停止; 若定时到&#xff0c;则停…

大数据技术 指令笔记1

3.cd命令 cd命令用来切换工作目录至DirName。其中DirName表示法可为绝对路径或相对路径 例如&#xff1a; cd/ 切换到根目录 cd 切换到家目录 cd /etc/sysconfig/ 切换到/etc/sysconfig目录 cd .. 返回到父目录 4.Is命令 Is命令用来列出文件或…