【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…

人生第一次面试之依托答辩

今天收到人生的第一场面试&#xff0c;是东华软件集团。答的那是依托答辩&#xff0c;就面了20分钟&#xff0c;还没考算法。其实依托答辩的效果是意料之中的&#xff0c;这次面试也只是想练练手。 目录 静态变量什么时候加载的&#xff1f; 重写和重载有什么区别&#xff1…

Elasticsearch:索引mapping

这里写目录标题 一、介绍二、动态mapping三、mapping属性&#xff08;1&#xff09;analyzer&#xff08;分析器&#xff09;(2) coerce&#xff08;强制类型转换&#xff09;&#xff08;3&#xff09;copy_to&#xff08;合并参数&#xff09; 一、介绍 二、动态mapping 三…

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

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

P8772 [蓝桥杯 2022 省 A] 求和

题目描述 给定 &#x1d45b; 个整数 &#x1d44e;1,&#x1d44e;2,⋯ ,&#x1d44e;&#x1d45b; 求它们两两相乘再相加的和&#xff0c;即 &#x1d446;&#x1d44e;1⋅&#x1d44e;2&#x1d44e;1⋅&#x1d44e;3⋯&#x1d44e;1⋅&#x1d44e;&#x1d45b;&…

快速学习 pytest 基础知识

全篇大概 5000 字&#xff08;含代码&#xff09;&#xff0c;建议阅读时间10min 简介 Pytest是一个非常成熟的测试框架&#xff0c;适用于但愿测试、UI测试、接口测试。 简单灵活、上手快支持参数化具有多个第三方插件可以直接使用 assert 进行断言 一、Pytest安装 pip inst…

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

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

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

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

Linux 内核中的 netif_start_queue 函数:启动网络接口发送队列的关键

在 Linux 内核的网络子系统中,netif_start_queue 函数扮演着至关重要的角色。这个函数的主要功能是启动(或启用)网络接口的发送队列,标志着网络接口已经准备好开始发送数据包。本文将深入探讨 netif_start_queue 函数的用途、工作原理以及在实际网络驱动代码中的应用。 函…

<代码随想录> 算法训练营-2025.01.06

今日专题&#xff1a;并查集 107. 寻找存在的路径 思路&#xff1a;采用并查集的结构&#xff0c;在把图录进去后&#xff0c;判断起点和终点的根是否一致 #其实bfs也可以做吧fa{} def find(u):if fa.get(u) None or fa[u]u:return uelse:fa[u]find(fa[u])return fa[u]def j…

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

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

LabVIEW无标题的模态VI窗口的白框怎么去除?

在LabVIEW中&#xff0c;如果你遇到无标题的模态&#xff08;modal&#xff09;VI窗口显示有一个白框&#xff0c;通常是由于VI界面的一些属性或者控件设置问题导致的。为了去除这个白框&#xff0c;可以尝试以下几种方法&#xff1a; 1. 检查VI窗口属性设置 确保你的VI窗口属…

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主成分分析降数据——抽样法数据压缩 数据预处理 数据预处理…

Go跨平台UI开发之wails的使用(1)

UI开发为了迁就同事&#xff0c;入坑了Go&#xff0c;虽然Go写UI程序是真的坑&#xff0c;不过&#xff0c;还是决定继续用吧。 第一版只有Windows版&#xff0c;用了webview2Vue的方案&#xff0c;webview2依赖如下&#xff1a; github.com/jchv/go-webview2 新版改造&#x…

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;系统中的抽象表示。它是构建逻辑卷组的基本单元 假设我们有…

Android 系统签名 keytool-importkeypair

要在 Android 项目中使用系统签名并将 APK 打包时与项目一起打包&#xff0c;可以按照以下步骤操作&#xff1a; 步骤 1&#xff1a;准备系统签名文件 从 Android 系统源码中获取系统签名文件&#xff0c;通常位于 build/target/product/security 目录下&#xff0c;包括 pla…