news 2026/4/23 11:20:39

从零到一:彻底搞懂 JavaScript 中的 CommonJS 规范

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零到一:彻底搞懂 JavaScript 中的 CommonJS 规范

一杯咖啡的时间,带你穿透 Node.js 模块系统的灵魂

你是否曾面对这样的代码陷入沉思:

constfs=require('fs');module.exports=classLogger{...};

为什么 Node.js 用require而非importexportsmodule.exports到底有何玄机?当两个模块互相引用时,程序为何没有崩溃?

这一切的答案,都藏在CommonJS—— 这个塑造了 Node.js 生态的模块规范之中。今天,让我们拨开迷雾,深入理解这个“古老却依然鲜活”的 JavaScript 模块基石。


一、为何而生:CommonJS 的历史使命

2009 年,JavaScript 还困于浏览器 sandbox。当 Ryan Dahl 构建 Node.js 时,他面临一个核心问题:如何让 JavaScript 在服务端优雅地组织代码?

彼时,浏览器端尚无标准模块方案(AMD/CMD 尚未普及),而服务端需要:

  • 同步加载(文件系统 I/O 快)
  • 简洁的导出/导入语法
  • 模块作用域隔离

CommonJS 规范应运而生。它并非 JavaScript 语言标准,而是一套社区驱动的模块约定。Node.js 选择将其作为默认模块系统,从此改变了 JavaScript 的命运轨迹。

💡 小知识:CommonJS 最初名为 “ServerJS”,后为避免局限性更名为 CommonJS。


二、核心三剑客:require、module、exports

每个 CommonJS 模块在执行前,Node.js 会隐式包裹如下代码:

(function(exports,require,module,__filename,__dirname){// 你的模块代码});

这五大参数构成了模块的“安全结界”。

🔑 导入:require()

constpath=require('path');// 核心模块constutils=require('./utils');// 文件模块(.js/.json/.node)constlodash=require('lodash');// node_modules 模块
  • 同步阻塞:适合服务端文件系统,不适合浏览器网络加载
  • 路径解析有优先级:核心模块 > 绝对路径 > 相对路径 > node_modules 递归查找
  • 返回值即 module.exports:require 本质是函数调用,返回目标模块导出的对象

📦 导出:exports 与 module.exports 的生死纠缠

// ✅ 正确:通过 exports 添加属性(修改原对象)exports.add=(a,b)=>a+b;exports.PI=3.14;// ✅ 正确:直接替换整个导出对象module.exports=classCalculator{...};// ❌ 致命错误:切断引用链!exports={name:'broken'};// 外部 require 仍得到空对象 {}

关键原理
exports本质是module.exports引用别名。初始时二者指向同一空对象{}

  • 当你exports.xxx = ...→ 修改共同指向的对象
  • 当你exports = ...→ 仅改变局部变量exports的指向,module.exports不变

🌰 比喻:module.exports是房子,exports是钥匙。你可以用钥匙装修房子(添加属性),但若把钥匙扔掉换新钥匙(重新赋值),房子本身没变。


三、深度机制:缓存、循环依赖与执行逻辑

🔄 模块缓存:性能的隐形守护者

Node.js 会将首次加载的模块结果缓存在require.cache中:

require('./config');// 首次执行,缓存结果require('./config');// 直接返回缓存,模块代码不再执行

调试技巧:开发时需热重载?可手动清除缓存:

deleterequire.cache[require.resolve('./config')];

(生产环境慎用!)

🔄 循环依赖:Node.js 的优雅解法

当 A require B,B 又 require A 时:

// a.jsconsole.log('A 启动');exports.flag=false;constb=require('./b');console.log('A 中看到 B.flag:',b.flag);// false(B 尚未执行完)exports.flag=true;// b.jsconsole.log('B 启动');exports.flag=false;consta=require('./a');// 此时拿到 A 当前已导出的部分(flag=false)console.log('B 中看到 A.flag:',a.flag);exports.flag=true;

输出顺序
A 启动 → B 启动 → B 中看到 A.flag: false → B.flag 设为 true → A 中看到 B.flag: true → A.flag 设为 true

Node.js 不会死锁,而是返回已执行部分的 module.exports 快照。但逻辑易错——最佳实践:重构代码避免循环依赖


四、实战场景与避坑指南

🛠️ 典型用法:构建工具模块

// logger.jsconstfs=require('fs');constpath=require('path');classLogger{constructor(name){this.name=name;this.logPath=path.join(__dirname,'logs',`${name}.log`);}info(msg){constline=`[${newDate().toISOString()}] [INFO]${msg}\n`;fs.appendFileSync(this.logPath,line);console.log(line.trim());}}// 导出构造函数(必须用 module.exports!)module.exports=Logger;
// app.jsconstLogger=require('./logger');constappLog=newLogger('app');appLog.info('服务启动成功');

⚠️ 高频陷阱清单

陷阱现象正确做法
exports = function() {}导出为空对象module.exports = function() {}
忽略.js后缀路径解析失败显式写require('./utils.js')
循环依赖中访问未初始化属性undefined重构依赖结构,或延迟访问
修改 require.cache 未处理错误进程崩溃添加 try/catch 保护

五、CommonJS vs ES Modules:时代的对话

维度CommonJSES Modules (ESM)
加载时机运行时同步编译时静态分析(支持异步)
导出本质值的拷贝实时绑定(live binding)
this 指向module.exportsundefined
Node.js 支持默认(.js 文件)需 .mjs 或 package.json 设"type": "module"
浏览器原生❌ 需打包工具✅ 原生支持<script type="module">

🌍 现状:Node.js 已全面支持 ESM(v12+),但 npm 上超 80% 的包仍提供 CommonJS 版本。二者将在未来长期共存。Webpack/Rollup 等工具能无缝转换,开发者需具备“双模认知”。


六、结语:站在巨人的肩膀上

CommonJS 或许没有 ESM 的“现代感”,但它用极简的设计哲学:

  • 用同步思维契合服务端场景
  • 用缓存机制保障性能
  • 用清晰的作用域隔离提升可维护性
    为 JavaScript 走出浏览器、构建庞大生态铺平了道路。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/24 17:10:54

深入浅出AQS(抽象队列同步器)

AQS是AbstractQueuedSynchronizer的简称&#xff0c;即抽象队列同步器&#xff0c;从字面上可以这样理解:抽象&#xff1a;抽象类&#xff0c;只实现一些主要逻辑&#xff0c;有些方法由子类实现&#xff1b;队列&#xff1a;使用先进先出&#xff08;FIFO&#xff09;的队列存…

作者头像 李华
网站建设 2026/4/23 11:20:34

【Java基础|Day04】Java数组详解:从定义到内存原理

【Java基础|Day04】Java数组详解&#xff1a;从定义到内存原理&#xff08;2026最新实用版&#xff09; Java数组是最基础、最常用的引用数据类型&#xff0c;几乎所有集合框架&#xff08;如ArrayList底层&#xff09;都依赖它。掌握数组 掌握了Java中连续内存 引用机制 堆…

作者头像 李华
网站建设 2026/4/17 14:19:38

Vue 双向绑定原理剖析

Vue 双向绑定原理剖析&#xff08;Vue 3 时代完整版&#xff0c;2026视角&#xff09; Vue 的“双向绑定”其实是两个方向的单向流动巧妙组合的结果&#xff1a; 数据 → 视图&#xff1a;响应式系统&#xff08;Reactivity&#xff09;自动更新 DOM&#xff08;Vue 2 用 Obj…

作者头像 李华
网站建设 2026/4/16 20:21:42

谷歌AlphaGenome登Nature封面!破译生命基因暗物质

AlphaGenome能够一次性读取 100 万个 DNA 碱基对&#xff0c;并以单碱基的惊人精度破译这一庞大序列中的调控密码&#xff0c;为人类理解占据基因组 98% 的非编码区域带来了史无前例的清晰视野。Google DeepMind 团队去年 6 月发布的 AlphaGenome 模型刚刚开源了。而且同步登上…

作者头像 李华
网站建设 2026/4/16 22:40:18

一个模型千万个灵魂!Anthropic找到了防止AI陷入疯狂的防线

Anthropic和牛津大学的最新研究发现&#xff0c;助手只是大模型在广阔人格空间中扮演的一个特定角色&#xff0c;而这个角色极其容易在长对话中发生漂移甚至崩塌。 细心的朋友应该注意到&#xff0c;大多数大模型的系统提示词都是&#xff1a;你是一个有用的助手&#xff08;Y…

作者头像 李华
网站建设 2026/4/17 15:14:21

AI革命者苏茨克维:从GPT之父到安全先锋

从AlexNet到GPT-4&#xff1a;一位AI巨匠的技术哲学与安全远征当世界还在为GPT-3的惊艳表现惊叹时&#xff0c;它的核心架构师却悄然转身&#xff0c;创办了一家专注于“安全超级智能”的公司——这是伊尔亚苏茨克维的选择&#xff0c;也是他对AI未来最深刻的警示。2012年的Ima…

作者头像 李华