无需登录 数据私有 本地保存

ES Module 与普通脚本对比演示 - 作用域与加载

10
0
0
0

ES Module vs 普通脚本

直观对比 普通脚本ES Module作用域隔离、this指向、严格模式、模块缓存等方面的关键差异

普通脚本 <script> 同步执行 · 全局作用域 · 非严格模式
同步执行,var变量会挂到window
ES Module <script type="module"> 异步执行 · 模块作用域 · 严格模式
异步执行,变量不泄漏到window
普通脚本输出
等待执行...
ES Module 输出
等待执行...
全局作用域泄漏检测 window 属性快照对比
检测项 普通脚本结果 ES Module 结果 对比
新增全局属性
通过对比执行前后 window 属性快照,检测变量是否泄漏到全局作用域
作用域
普通脚本共享全局作用域,var变量挂到window;ES Module拥有独立模块作用域
this 指向
普通脚本顶层this指向window;ES Module顶层thisundefined
严格模式
普通脚本默认非严格模式;ES Module自动启用严格模式,未声明变量会报错
加载与缓存
普通脚本同步加载可重复执行;ES Module异步加载(defer),同URL只执行一次
常见问题与知识点 FAQ

最核心的区别有三点:
1. 作用域隔离:ES Module 拥有独立的模块作用域,内部声明的变量(包括var)不会泄漏到全局 window 对象;而普通脚本中的 var 声明会直接挂到 window 上。
2. 严格模式:ES Module 自动以严格模式运行(无需手动写 'use strict'),未声明的变量赋值会抛出 ReferenceError
3. 加载时机:ES Module 默认具有 defer 行为,异步加载且不阻塞HTML解析;普通脚本同步加载,会阻塞页面渲染。

这是 ES Module 规范的设计决策。在模块系统中,顶层 this 被定义为 undefined,目的是防止模块代码意外访问全局对象,增强代码的封装性和安全性。相比之下,普通脚本中顶层 this 指向 window(浏览器环境),容易导致意外的全局状态修改。这一差异也使得模块代码更容易在不同环境(浏览器、Node.js)之间移植。
浏览器对 ES Module 使用模块映射(Module Map)进行缓存。当一个模块 URL 被首次加载后,其导出内容会被缓存。后续任何对相同 URL 的导入(无论是静态 import 还是动态 import())都会直接返回缓存,不会重新执行模块代码。这意味着:
• 模块内的初始化代码只执行一次
• 多个模块导入同一个URL时共享同一个模块实例
• 普通脚本则没有此缓存机制,每次添加到DOM都会重新执行

可以,但有一定限制。ES Module 可以使用 import 语句导入任何 JavaScript 文件。如果导入的是一个普通脚本(不含 export),该脚本仍会在模块上下文中执行(严格模式、模块作用域),但不会有任何导出内容。通常更好的做法是使用动态导入 import() 来加载普通脚本,或将其改造为 ES Module 格式。

静态 import:在模块顶层使用,语法为 import { foo } from './module.js',在编译时解析,必须位于模块顶层,不能条件加载。
动态 import():返回一个 Promise,可以在任何地方调用(包括普通脚本),支持条件加载、懒加载、按需加载。例如:const module = await import('./module.js')。动态导入同样遵循模块缓存规则。

ES Module 在现代浏览器中得到广泛支持(Chrome 61+、Firefox 60+、Safari 11+、Edge 16+)。对于需要兼容旧版浏览器的项目,可以使用 <script nomodule> 提供降级方案,或使用打包工具(如 Webpack、Rollup、esbuild)将模块代码编译为兼容格式。截至2024年,ES Module 的全球浏览器支持率已超过96%

ES Module:静态导入/导出,编译时确定依赖关系,支持 tree-shaking,异步加载,浏览器和现代Node.js原生支持。
CommonJS:动态 require(),运行时加载,同步加载,主要用于Node.js(传统)。
关键差异:CommonJS 导出的是值的拷贝,ES Module 导出的是值的引用(实时绑定)。Node.js 从 v12+ 开始原生支持 ES Module(需使用 .mjs 扩展名或在 package.json 中设置 "type": "module")。