切换导航
简历
工具
演示
查询
导航菜单
服务端
30
前端
114
数据库
82
进阶开发
8
桌面程序
2
QA
15
软件使用
36
规范协议
5
操作系统
11
AI
0
复用资源
6
硬件
0
标签
HTML
正则表达式
Json
包管理工具
vite
CSS
XPath
JavaScript
Jquery
less
ES6
Vue
React
vue 3
Layui
editor.md
sea.js
Zepto.js
mysql.js
mssql.js
NodeJs
Express
前端template
YAML
杂项
外部链接
.NET Core SDK和运行时安装包
淘宝 NPM 镜像
查询网
Tables Generator线上表格转换工具
引用cdn在线脚本和样式
首页
前端
JavaScript 定义的模块化规范 CommonJS与ECMAScript
[TOC] <h2>Javascript 两个模块规范</h2> ## 1. ES Modules 模块 ES Modules 简写 `ESM`,这个是ES6 (ECMAScript 2015) 引入了官方的模块化语法 ### 1.1. ESM 的核心特性 |特性|说明| |--|--| |静态结构|import 和 export 必须在代码顶层,不能放在条件或函数中,便于静态分析| |异步加载|模块代码的获取和解析是异步的,适合浏览器(防止阻塞)| |实时绑定|导入的是原始值的动态只读引用(类似指针),而不是拷贝| |支持 Tree Shaking|由于静态结构,打包工具可以剔除未使用的导出,减小体积| |严格模式|ESM 默认在严格模式下运行,无需 "use strict"| |独立作用域|每个模块有自己的顶级作用域,不会污染全局| ### 1.2. ESM 的使用方式 #### 1.2.1. 定义全局模式(浏览器与 Node.js) **浏览器:**在 `<script>` 标签上添加 type="module"。 **Node.js:**文件扩展名使用 .mjs,或者在 package.json 中设置 "type": "module"(此时 .js 文件按 ESM 解析)。 **原生支持:**现代浏览器和 Node.js 13+ 版本都原生支持 ESM。 #### 1.2.2. 导出(Export) **1️⃣命名导出(Named Export)** ```js // 📁 utils.js // 分别导出 export const PI = 3.14159; export function add(a, b) { return a + b; } export class Circle { /* ... */ } // 也可以集中导出 const name = 'ESM'; const version = 1.0; export { name, version }; ``` **2️⃣默认导出(Default Export)** ```js // 📁 logger.js export default function log(message) { console.log(`[LOG] ${message}`); } // 也可以导出对象/类等 // export default { a: 1 }; ``` > Tips:每个模块只能有一个默认导出。 **3️⃣混合导出(命名 + 默认)** ```js // 📁 mixed.js export default function main() { /* ... */ } export const helper = () => {}; ``` #### 1.2.3. 导入(Import) **1️⃣导入命名导出** ```js // 📁 app.js import { PI, add, Circle } from './utils.js'; console.log(PI); // 3.14159 console.log(add(2, 3)); // 5 ``` **2️⃣导入默认导出** ```js import log from './logger.js'; log('Hello'); // [LOG] Hello ``` **3️⃣同时导入默认和命名** ```js import main, { helper } from './mixed.js'; main(); helper(); ``` **4️⃣全部导入(命名空间导入)** ```js import * as Utils from './utils.js'; console.log(Utils.PI); // 3.14159 console.log(Utils.add(1, 2)); // 3 // 注意:Utils.default 如果模块有默认导出,会绑定到 default 上 ``` **5️⃣仅执行模块(不需要导入任何内容)** ```js import './init.js'; // 执行 init.js 中的代码,但不导入符号 ``` **6️⃣动态导入(import())** ```js // 📁 dynamic.js button.addEventListener('click', async () => { const module = await import('./heavy.js'); module.run(); }); ``` > Tips:返回 Promise,可以在运行时按需加载。这是 ESM 唯一允许在逻辑内部使用的导入形式。 #### 1.2.4. 重命名导入/导出 ```js // 导出时重命名 export { add as sum }; // 导入时重命名 import { sum as addFunction } from './utils.js'; ``` #### 1.2.5. 模块的路径规范 + 1️⃣相对路径:'./a.js'、'../lib/b.js' —— 必须带扩展名 .js(浏览器要求完整路径)。 + 2️⃣绝对路径:'/static/js/c.js'。 + 3️⃣URL:'https://example.com/module.js'。 + 4️⃣裸模块(如 'lodash'):需要在 Node.js 或打包工具中配置 resolve 规则;原生浏览器不支持(除非使用 Import Maps)。 ## 1.3. ESM 解决了哪些问题 |问题|ESM 的解决方案| |--|--| |全局污染|每个模块独立作用域,变量不泄露到全局| |依赖顺序难以管理|`import` 声明自动解析依赖并确保加载顺序| |无法按需加载|动态 `import()` 实现代码分割和懒加载| |重复加载|模块只执行一次,导出的是单例| |难以进行静态优化(Tree shaking)|静态结构使得打包工具可以分析并剔除未使用的导出| |服务端与客户端模块格式不统一|ESM 是官方标准,同时支持浏览器和 Node.js,统一了生态| ## 1.4. 示例与详细解释 ### 1.4.1. 基本数学模块(展示命名导出) ```js // 📁 math.js export const sum = (a, b) => a + b; export const product = (a, b) => a * b; // 📁 main.js import { sum, product } from './math.js'; console.log(sum(2, 3)); // 5 console.log(product(2, 3)); // 6 ``` **解释:** + 1️⃣export const ... 分别导出了两个函数。 + 2️⃣import { ... } from ... 通过命名导入方式获取它们。 + 3️⃣因为是静态导入,解析 main.js 时就会加载 math.js,并且两者共享实时绑定(但不是重点,这里只是值引用?实际上函数引用也是动态的,但函数本身不可变,效果类似)。 ### 1.4.2. 默认导出 + 命名导出混合 ```js // 📁 user.js export default class User { constructor(name) { this.name = name; } } export function greet(user) { console.log(`Hello, ${user.name}`); } // 📁 app.js import User, { greet } from './user.js'; const me = new User('Alice'); greet(me); // Hello, Alice ``` **解释:** + 1️⃣export default class User 提供了默认导出(一个类)。 + 2️⃣export function greet 提供了命名导出。 + 3️⃣导入时可以分别接收:默认导入不需要花括号,命名导入需要。混合使用非常常见。 ### 1.4.3. 动态导入实现懒加载 ```js // 📁 lazy.js export function heavyComputation() { let result = 0; for (let i = 0; i < 1e9; i++) result += i; return result; } // 📁 index.html 中的 <script type="module"> document.getElementById('computeBtn').addEventListener('click', async () => { const { heavyComputation } = await import('./lazy.js'); console.log(heavyComputation()); // 仅在点击时才加载并执行 }); ``` **解释:** + 1️⃣import() 返回 Promise,模块代码会异步获取和解析。 + 2️⃣只有用户点击按钮时才会下载 lazy.js,避免了初始加载时执行大量计算。 + 3️⃣实际项目中常用于路由懒加载、按需加载组件。 ### 1.4.4. 实时绑定(与 CJS 的关键区别) ```js // 📁 counter.js (ESM) export let count = 0; export function increment() { count++; } // 📁 main.js import { count, increment } from './counter.js'; console.log(count); // 0 increment(); console.log(count); // 1 🎉 实时绑定的效果! setTimeout(() => { console.log(count); // 假如再调用 increment,这里也会变 }, 1000); ``` **解释:** + 1️⃣CJS 导出的是值的拷贝,外部无法感知模块内部变化。 + 2️⃣ESM 导出的是实时只读引用,所以 main.js 中的 count 总是指向 counter.js 中 count 变量的当前值。 + 3️⃣但注意:不能对导入的变量重新赋值(比如 count = 10 会报错),但如果是对象,可以修改其属性。 ### 1.4.5. 模块的单例性 ```js // 📁 db.js class Database { constructor() { console.log('Connecting...'); } } export const db = new Database(); // 📁 a.js import { db } from './db.js'; // db 实例已经创建 // 📁 b.js import { db } from './db.js'; // 复用同一个实例,不会再创建新连接 ``` 解释: + 1️⃣无论多少个文件导入同一个模块,该模块的代码只执行一次,并缓存导出。 + 2️⃣因此,ESM 天然支持单例模式,适合共享状态、数据库连接池等。 ### 1.4.6. 浏览器中使用 ESM(HTML) ```HTML <!-- index.html --> <script type="module"> import { add } from './math.js'; console.log(add(2, 3)); // 5 </script> <!-- 或者导入并执行外部脚本 --> <script type="module" src="./main.js"></script> ``` 解释: + 1️⃣type="module" 告诉浏览器将代码当作模块处理。 + 2️⃣模块脚本默认是 defer(延迟执行,等文档解析完成后再按顺序执行)。 + 3️⃣模块内的 import 会触发跨域请求,需要服务器设置正确的 CORS 头(同源或支持跨域)。 ## 2. CommonJs 模块 commonJS 简写 `CJS`,CJS 是 Node.js 默认采用的模块系统。它使用两个核心的全局对象来定义和加载模块: + require():用于导入其他模块提供的功能。 + module.exports 或 exports:用于导出当前模块的功能。 ```js // ----- math.js (导出模块) ----- // 导出单个函数 const add = (a, b) => a + b; module.exports = add; // 或者导出一个包含多个属性和方法的对象 // module.exports = { add, subtract }; // ----- app.js (导入模块) ----- // 导入 math.js 模块导出的内容 const addFunction = require('./math.js'); const result = addFunction(5, 3); // result = 8 console.log(result); ``` ## 3. CommonJs VS ESM |特性|ESM|CJS| |--|--|--| |加载时机|静态解析 + 可选动态加载|运行时同步加载| |语法|import/export|require/module.exports| |值传递方式|实时只读引用|值的拷贝| |适用环境|浏览器、Node.js(原生)|Node.js(需借助工具用于浏览器)| |静态分析/编译时分析|✅ 支持 (可以在编译时确定模块依赖关系,有利于 Tree Shaking)|❌ 不支持 (在运行时动态加载)| |默认严格模式|✅ 是|❌ 需要手动 "use strict"| ## 扩展 Tree Shaking Tree Shaking 直译「摇树」,是前端工程化里的代码优化技术,是`打包工具的优化能力`,基于 ES6 Module 的构建期代码精简优化技术。 **形象理解:**把项目代码看成一棵大树,没用到的代码、死代码就是枯枝叶,Tree Shaking 在打包构建时「摇掉」这些无用枝叶,只打包实际被引用、执行的有效代码。 **目前支持工具:**Webpack、Rollup、Vite、esbuild(依赖前提:必须是 ES6 Module(import /export)模块化语法) **不支持:**<span style="color:red">CommonJS ,CommonJS 运行时加载,无法静态分析依赖</span> <table><tr><td><table><tr><th>核心作用</th></tr><tr><td>删除未使用的冗余代码</td></tr><tr><td>精简打包产物体积</td></tr><tr><td>提升加载与运行性能</td></tr><tr><td>减少冗余依赖体积</td></tr></table></td><td><table><tr><th>能解决什么问题</th></tr><tr><td>解决「引入全量库、只用部分能力」的冗余问题</td></tr><tr><td>解决项目中「废弃代码、注释残留、未引用模块」堆积问题</td></tr><tr><td>解决多文件合并打包后的体积膨胀问题</td></tr><tr><td>间接优化:减少浏览器解析、JS 执行耗时</td></tr></table></td></tr></table> > Tips:开发环境正常是不需要进行 Tree Shaking 的,使用打包工具打包时才会进行 Tree Shaking **Tree Shaking 失效的6中可能** |原因|示例| |--|--| |使用了 CommonJS(require/module.exports):<br>这是最核心、最常见的失效原因|| |代码被标记为【有副作用】(sideEffects):如果工具认为你的代码 “不能随便删”,就不会摇树,典型情况如下:<br> 1️⃣模块直接执行全局操作(修改 window、赋值全局变量、立即执行函数)<br>2️⃣package.json 里 sideEffects 配置错误<br>3️⃣导入但不使用的模块带副作用|| |使用了 Babel/@rollup/plugin-commonjs 把 ES6 转成了 CommonJS:<br>老版本 Babel 预设会把 import/export 编译成 require,直接破坏 Tree Shaking|| |动态导入、动态取值(无法静态分析):<br>Tree Shaking 是编译时优化,不是运行时。任何动态行为都会让它无法判断代码是否被使用。|| |导出未使用的【默认导出 / 混合导出】:<br>默认导出(export default)本身不影响,但搭配错误写法会失效|| |生产环境配置错误<br> 1️⃣没开启 mode: production(Webpack)<br>2️⃣没开启 compress / 压缩<br>3️⃣使用了不支持 Tree Shaking 的插件 /loader| | > Tips:只用 ES Module,禁止 CommonJS 、package.json 正确配置 sideEffects 、Babel 关闭 modules 转换 、生产环境 + 代码压缩
扫一扫手机看
点击排行
windows 文件资源管理器使用
Layui 弹窗open使用说明
SQL Server 中字符串函数使用
C# 使用QQ邮箱代理做邮件接收功能
SQL Server关于apply的两种形式cross apply 和 outer apply
ASP.NET MVC 模型的自定义验证
nvm安装及常用命令
SQL Server 中数据类型转换
SQL Server 中WITH AS短语的使用
IE浏览器下AJAX缓存问题导致数据不更新的解决办法
订阅号