浏览器与node.js模块执行上下文存在本质差异:作用域与this指向不同、导入导出机制不互通、顶层await约束不同、模块缓存键规则不同。

模块执行上下文是理解 JS 代码如何被加载、解析和运行的关键。浏览器和 Node.js 虽然都支持模块化,但它们的模块系统设计目标不同——浏览器面向前端资源加载与隔离,Node.js 面向服务端可维护性与文件级复用,这直接导致模块执行上下文存在本质差异。
模块默认作用域与 this 指向不同
在浏览器中,使用 <script type="module"></script> 加载的 ES 模块会自动进入严格模式,且每个模块拥有独立的顶层作用域。模块内未声明的变量不会挂到全局对象上,this 在模块顶层为 undefined(严格模式下)。
在 Node.js 中,每个 .js 文件默认被视为一个 CommonJS 模块,具有隐式的函数包裹(IIFE),模块顶层的 this 指向 module.exports,而非 global。这意味着:
-
this === module.exports为 true -
this === exports也为 true(初始时) - 直接给
this.a = 1等价于module.exports.a = 1
模块导入导出机制不互通
浏览器原生只支持 ES 模块(import/export),路径必须带扩展名或以 /、./、../ 开头,不支持自动解析 .js 后缀。
Node.js 默认支持 CommonJS(require/module.exports),同时也支持 ES 模块(需文件后缀为 .mjs 或 package.json 中设置 "type": "module")。但两者不能混用:
- CommonJS 无法
importES 模块(除非通过createRequire包装) - ES 模块无法
requireCommonJS 模块(但可import,Node 会自动适配其module.exports) -
import()动态导入在两者中都可用,但返回 Promise,在 Node 中支持 JSON/Worker 等额外类型
顶层 await 的行为一致但限制不同
ES 模块支持顶层 await,浏览器和 Node.js 都允许在模块顶层直接写 await fetch(...),模块执行会暂停直到 Promise 解决。
但实际约束有差别:
- 浏览器中,顶层 await 仅限静态模块图(即不能出现在动态
import()的回调里) - Node.js 中,启用顶层 await 的模块必须明确标记为
type: "module",且父模块也需是 ES 模块 - CommonJS 模块(
require)完全不支持顶层 await
模块缓存与重用逻辑不同
两者都对已加载模块做缓存,但缓存键规则不同:
- 浏览器以完整 URL(含查询参数)为缓存键,
import './a.js?v=1'和'./a.js?v=2'被视为两个模块 - Node.js 以文件绝对路径为缓存键,忽略查询参数;但可通过
require.resolve.paths或自定义loader干预解析逻辑 - Node.js 中
require('fs')总是返回同一份内置模块实例;浏览器中无等价内置模块机制,所有模块均为显式加载











