談談 Node.js 中的模塊系統,CommonJS 和 ES Modules 的區別是什么?


Node.js 模塊系統:CommonJS 和 ES Modules 核心差異與實戰指南

一、模塊系統基礎概念

**CommonJS (CJS)**? 是 Node.js 傳統模塊系統,采用同步加載方式,典型特征:

// 導出
module.exports = { name: 'cjs' };  // 或 exports.name = 'cjs'// 導入
const moduleA = require('./moduleA');  // 動態語法

**ES Modules (ESM)**? 是 ECMAScript 標準模塊系統,采用異步加載,典型特征:

// 導出
export const name = 'esm';  // 命名導出
export default { version: 1 };  // 默認導出// 導入
import moduleB, { name } from './moduleB.mjs';  // 靜態語法
二、7 個關鍵差異點(附代碼驗證)
1. 語法與加載機制
  • ?CJS 動態加載:允許條件語句中 require
if (Math.random() > 0.5) {require('./moduleA');  // 運行時決定加載
}
  • ?ESM 靜態分析:import 必須頂層聲明
// 報錯:import 必須位于模塊頂部
if (condition) { import './moduleB.mjs' } 
2. 模塊作用域差異
  • ?CJS 非嚴格模式:變量可隱式創建全局變量
// module-cjs.js
undeclaredVar = 100;  // 不報錯,污染全局
  • ?ESM 嚴格模式:禁止隱式全局變量
// module-esm.mjs
undeclaredVar = 100; // 報錯:未定義變量
3. 循環引用處理
  • ?CJS 動態引用:可能拿到未初始化的模塊
// a.js
exports.loaded = false;
const b = require('./b');
console.log('在a中,b.loaded =', b.loaded);  // true
exports.loaded = true;// b.js
exports.loaded = false;
const a = require('./a');
console.log('在b中,a.loaded =', a.loaded);  // false
exports.loaded = true;// 執行 node a.js → 輸出順序:
// 在b中,a.loaded = false
// 在a中,b.loaded = true
  • ?ESM 靜態綁定:引用指向最新值(類似指針)
// a.mjs
import { loaded } from './b.mjs';
export let loaded = false;
console.log('在a中,b.loaded =', loaded);  // true
loaded = true;// b.mjs
import { loaded } from './a.mjs';
export let loaded = false;
console.log('在b中,a.loaded =', loaded);  // false
loaded = true;// 執行 node a.mjs → 報錯(循環引用需特殊處理)
4. 頂層 this 指向
  • ?CJS 的 this? 指向?module.exports?對象
console.log(this === module.exports);  // true
  • ?ESM 的 this? 為?undefined(嚴格模式)
console.log(this);  // undefined
5. 文件擴展名與配置
  • ?CJS? 默認識別?.js?和?.cjs?文件
  • ?ESM? 需要以下條件之一:
    • 文件后綴為?.mjs
    • 最近的?package.json?中設置?"type": "module"
// package.json
{"type": "module"  // 項目內 .js 文件默認視為 ESM
}
6. 引用類型差異
  • ?CJS 導出值拷貝:基本類型值復制,對象類型淺拷貝
// cjs-module.js
let count = 1;
setTimeout(() => { count = 2 }, 100);
module.exports = { count };// main.js
const { count } = require('./cjs-module');
console.log(count);  // 1
setTimeout(() => console.log(count), 200);  // 仍為1
  • ?ESM 動態綁定:始終獲取最新值
// esm-module.mjs
export let count = 1;
setTimeout(() => { count = 2 }, 100);// main.mjs
import { count } from './esm-module.mjs';
console.log(count);  // 1
setTimeout(() => console.log(count), 200);  // 變為2
7. 動態導入能力
  • ?CJS? 原生不支持動態導入,但可通過?require?實現
  • ?ESM? 支持?import()?動態導入(返回 Promise)
// 動態加載 ESM 模塊
const module = await import('./module.mjs');// 動態加載 CJS 模塊(在 ESM 中)
import cjsModule from './cjs-module.cjs';  // 需完整后綴

三、日常開發建議
1. 新項目技術選型
  • ?優先使用 ESM:符合語言標準,支持 Tree Shaking
// package.json
{"type": "module","scripts": {"start": "node --experimental-vm-modules src/index.mjs"}
}
2. 舊項目遷移策略
  • ?漸進式遷移
    • 將單個文件后綴改為?.mjs?或設置?"type": "module"
    • 使用?import/export?語法逐步替換
// 混合使用示例(在 ESM 中引入 CJS)
import cjsModule from './legacy-module.cjs';  // 注意后綴
3. 模塊兼容性處理
  • ?雙格式發布庫:通過?package.json?指定雙入口
{"exports": {"import": "./esm-module.mjs","require": "./cjs-module.cjs"}
}
4. 避免踩坑指南
  • ?禁用默認互操作:CJS 默認導出需特別注意
// ESM 導入 CJS 模塊
import cjsModule from './cjs-module.cjs';  // module.exports 整體作為默認導出
  • ?循環引用處理:ESM 中建議使用函數封裝初始化邏輯
// a.mjs
import { initB } from './b.mjs';
export let valueA = '未初始化';export function initA() {valueA = '初始化A';initB();
}// b.mjs
import { initA } from './a.mjs';
export let valueB = '未初始化';export function initB() {valueB = '初始化B';initA();  // 安全調用
}

四、注意事項
  1. ?全局變量替換
    ESM 中無法直接使用?__dirname,需改用:

    import { fileURLToPath } from 'url';
    const __dirname = path.dirname(fileURLToPath(import.meta.url));
  2. ?文件擴展名強制要求
    在 ESM 中引入文件時必須寫完整擴展名:

    import './utils.js';  // 必須寫 .js
  3. ?默認導出差異
    CJS 的?module.exports?對應 ESM 的默認導出:

    // CJS 模塊
    module.exports = { a: 1 };// ESM 導入方式
    import cjsModule from './cjs-module.cjs';  // { a: 1 }
  4. ?性能優化
    ESM 的靜態分析特性使打包工具(如 Rollup)能實現更高效的 Tree Shaking。


五、總結

理解兩種模塊系統的核心差異,能幫助開發者根據場景合理選擇:

  • ?CJS? 適合傳統 Node.js 項目、需要動態加載的場景
  • ?ESM? 適合現代瀏覽器兼容項目、需要靜態分析的構建優化

在混合項目中,通過文件擴展名和?package.json?配置明確模塊類型,避免隱式錯誤。對于長期維護的項目,逐步向 ESM 遷移是更符合技術趨勢的選擇。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/72044.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/72044.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/72044.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【HarmonyOS Next】 鴻蒙應用useNormalizedOHMUrl詳解

【HarmonyOS Next】 鴻蒙應用useNormalizedOHMUrl詳解 一、useNormalizedOHMUrl是什么? useNormalizedOHMUrl指的是是否使用標準化OHMUrl拼接。 在開發過程中,需要根據不同的環境或配置動態生成 URL。例如,在加載一些遠程模塊或者資源時,…

wav格式的音頻壓縮,WAV 轉 MP3 VBR 體積縮減比為 13.5%、多個 MP3 格式音頻合并為一個、文件夾存在則刪除重建,不存在則直接建立

🥇 版權: 本文由【墨理學AI】原創首發、各位讀者大大、敬請查閱、感謝三連 🎉 聲明: 作為全網 AI 領域 干貨最多的博主之一,?? 不負光陰不負卿 ?? 文章目錄 問題一:wav格式的音頻壓縮為哪些格式,網絡傳輸給用戶播放…

MFC線程

創建線程 HANDLE m_hThread; m_hThread CreateThread(NULL, 0, save_snapshot, (LPVOID)this, 0, &iThreadId);開啟線程循環等待 DWORD WINAPI save_snapshot(LPVOID pVoid) {while (true){//持續循環等待事件到達。接收到事件信號后才進入if。if (::WaitForSingleObjec…

賦能農業數字化轉型 雛森科技助力“聚農拼”平臺建設

賦能農業數字化轉型,雛森科技助力“聚農拼”平臺建設 在數字化浪潮席卷各行業的今天,農業領域也在積極探索轉型升級之路。中農集團一直以“根植大地,服務三農”為核心,以“鄉村振興,農民增收”為目標,及時…

千峰React:Hooks(上)

什么是Hooks ref引用值 普通變量的改變一般是不好觸發函數組件的渲染的,如果想讓一般的數據也可以得到狀態的保存,可以使用ref import { useState ,useRef} from reactfunction App() {const [count, setCount] useState(0)let num useRef(0)const h…

Ubuntu20.04安裝Redis

1.切換到root用戶 如果沒有切換到root用戶的,切換到root用戶。 2.使用 apt install redis 安裝redis 遇到y/n直接y即可。 redis安裝好之后就自動啟動起來了,因此我們可以通過netstat -anp | grep redis命令來查看是否安裝成功。 6379是Redis的默認端…

鴻蒙-AVPlayer

compileVersion 5.0.2(14) 音頻播放 import media from ohos.multimedia.media; import common from ohos.app.ability.common; import { BusinessError } from ohos.base;Entry Component struct AudioPlayer {private avPlayer: media.AVPlayer | nu…

機器學習數學通關指南——泰勒公式

前言 本文隸屬于專欄《機器學習數學通關指南》,該專欄為筆者原創,引用請注明來源,不足和錯誤之處請在評論區幫忙指出,謝謝! 本專欄目錄結構和參考文獻請見《機器學習數學通關指南》 正文 一句話總結 泰勒公式是用多…

游戲引擎學習第124天

倉庫:https://gitee.com/mrxiao_com/2d_game_3 回顧/復習 今天是繼續完善和調試多線程的任務隊列。之前的幾天,我們已經介紹了多線程的一些基礎知識,包括如何創建工作隊列以及如何在線程中處理任務。今天,重點是解決那些我們之前沒有注意到…

在MacOS上打造本地部署的大模型知識庫(一)

一、在MacOS上安裝Ollama docker run -d -p 3000:8080 --add-hosthost.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main 最后停掉Docker的ollama,就能在webui中加載llama模…

(八)Java-Collection

一、Collection接口 1.特點 Collection實現子類可以存放多個元素,每個元素可以是Object; 有些Collection的實現類,可以存放重復的元素,有些不可以; 有些Collection的實現類,有些是有序的(Li…

大模型RAG(檢索增強)創新--SELF-RAG

檢索增強生成 (RAG) 提供了一種將 ChatGPT/GPT-4 等大型語言模型與自定義數據集成的途徑,但存在局限性。讓我們看看 RAG 最近的研究是如何解決一些問題。 大語言模型(LLM)將改變整個金融領域。其中一個場景是大語言模型可以學習大量文檔,并在很短的時間內…

《AI和人工智能和編程日報》

OpenAI:將深度研究擴展到 ChatGPT Plus、Team、Edu 和 Enterprise 用戶,每月 10 次查詢;Pro 用戶每月有 120 次查詢,ChatGPT 語音模式向免費用戶開放。DeepSeek:R1 大模型宣布降價,調用價格將至四分之一&am…

【音視頻】編解碼相關概念總結

NALU RTP PS流 三者總體關系 NALU在RTP中的應用:視頻流的RTP傳輸通常將NALU作為基本的單元進行傳輸。每個RTP包攜帶一個或多個NALU,這些NALU包含了視頻編碼數據。RTP協議通過其頭部信息(如時間戳、序列號等)幫助接收端重新排列和…

端口映射/內網穿透方式及問題解決:warning: remote port forwarding failed for listen port

文章目錄 需求:A機器是內網機器,B機器是公網服務器,想要從公網,訪問A機器的端口方式:端口映射,內網穿透,使用ssh打洞端口:遇到問題:命令執行成功,但是端口轉發…

11特殊函數

一、遞歸函數 遞歸概念:如果一個函數內部,包含了對自身的調用,則該函數稱為遞歸函數。要點: 只有能被表達為遞歸的問題,才能用遞歸函數解決。遞歸函數必須有一個可直接退出的條件,否則會進入無限遞歸。遞歸…

如何使用useContext進行全局狀態管理?

在 React 中,使用 useContext 進行全局狀態管理是一種有效的方法,尤其在需要在多個組件之間共享狀態時。useContext 允許你在組件樹中傳遞數據,而無需通過每個組件的 props 逐層傳遞。以下是關于如何使用 useContext 進行全局狀態管理的詳細指…

鴻蒙 ArkUI 實現敲木魚小游戲

敲木魚是一款具有禪意的趣味小游戲,本文將通過鴻蒙 ArkUI 框架的實現代碼,逐步解析其核心技術點,包括動畫驅動、狀態管理、音效震動反饋等。 一、架構設計與工程搭建 1.1 項目結構解析 完整項目包含以下核心模塊: ├── entry…

神經性肺纖維的預防方法

神經性肺纖維的預防方法 一、引言 神經性肺纖維化是一種慢性進行性肺部疾病,其病因復雜,包括遺傳、環境等多種因素。該病不僅影響患者的呼吸功能,還可能對神經系統造成損害。因此,預防神經性肺纖維化顯得尤為重要。本文將詳細介…

azure sql 網絡安全組 網絡安全sql注入

🍅 點擊文末小卡片 ,免費獲取網絡安全全套資料,資料在手,漲薪更快 SQL注入 1、原理 針對注入的攻擊行為可描述為通過用戶可控參數中注入SQL語法,破壞原有SQL結構,達到編寫程序意料之外結果的攻擊行為。 其…