前端小白也能懂:ES模块和CommonJS的那些事

简介: 【6月更文挑战第1天】在JavaScript的世界中,模块化是构建大型应用的关键。ES模块(ESM)和CommonJS是两种主流的模块系统,它们各自有着不同的特性和使用场景。你了解它们的区别吗?

在JavaScript的世界中,模块化是构建大型应用的关键。ES模块(ESM)和CommonJS是两种主流的模块系统,它们各自有着不同的特性和使用场景。你了解它们的区别吗?

ES模块 (ESM)

ES模块是 ECMAScript 官方标准的一部分,它使用importexport语句来导入和导出模块。ES 模块是 JavaScript 官方标准的一部分,已被现代浏览器和 JavaScript 运行时(如Node.js)所支持。

主要特点

  1. 静态结构:ES模块在编译时就确定了模块的依赖关系,可以进行静态分析。
  2. 模块作用域:每个模块都有自己的作用域,默认情况下,模块中的变量不会泄漏到全局作用域。
  3. 异步加载:在浏览器环境中,ES模块可以异步加载,有助于提高性能。

模块解析

  • ES模块的文件扩展名通常是 .js.mjs,其中 .mjs 明确表示该文件是一个ES模块。
  • 在Node.js中,你需要在 package.json 中设置 "type": "module" 来启用ES模块支持。

代码示例

// module.js
export function foo() {
   
    console.log("foo");
}

export const bar = () => {
   
    console.log("bar");
}

// main.js
import {
    foo, bar } from './module.js';

foo(); // 输出 "foo"
bar(); // 输出 "bar"

CommonJS模块

CommonJS模块是Node.js默认的模块系统,它使用requiremodule.exports来导入和导出模块。

主要特点

  • 动态结构:CommonJS模块在运行时确定模块的依赖关系,不能进行静态分析。
  • 同步加载:在Node.js中,CommonJS模块是同步加载的。
  • 广泛使用:由于Node.js默认采用CommonJS模块系统,所以在Node.js生态系统中广泛使用。
// module.js
function foo() {
   
    console.log("foo");
}
module.exports = {
    foo };

// main.js
const {
    foo } = require('./module.js');
foo(); // 输出 "foo"

缓存机制

CommonJS模块在第一次加载时会被缓存,后续加载同一个模块时会返回缓存的版本。这有助于提高性能,但也意味着模块的初始化代码只会执行一次。

 // module.js
 console.log("Module loaded");
 module.exports = {
    foo: "bar" };

 // main.js
 const module1 = require('./module.js');
 const module2 = require('./module.js');
 // 只输出一次 "Module loaded"

循环依赖

CommonJS模块支持循环依赖,但需要注意的是,循环依赖可能会导致模块加载顺序和结果不确定。

 // a.js
 const b = require('./b.js');
 console.log('a.js');
 module.exports = {
    name: 'module a' };

 // b.js
 const a = require('./a.js');
 console.log('b.js');
 module.exports = {
    name: 'module b' };

 // main.js
 require('./a.js');
 require('./b.js');
 // 输出顺序: a.js -> b.js

区别

主要的区别如下表所示:

特性 ES模块 (ESM) CommonJS模块
语法 import / export require / module.exports
加载方式 异步 (浏览器) 同步
静态分析 支持,有利于Tree Shaking 不支持,难以优化死代码
Tree Shaking 自然支持,提升代码体积优化 部分支持(需工具辅助,效果有限)
Top-Level Await 支持,便于编写异步初始化代码 不支持,需包裹在async函数内
作用域 模块作用域,提升封装性 文件作用域,可能导致污染全局
使用场景 浏览器和现代Node.js (>=13.2, 需配置) 传统Node.js及大量现有生态
模块循环依赖 处理更为严格,可能导致错误 更宽松,但可能隐藏逻辑问题

Tree Shaking 是什么?

  • ES模块自然支持Tree Shaking(枯树摇动),这是一种在打包过程中移除未被引用的代码的技术,有助于减小最终打包文件的体积。这是因为ES模块的静态结构使得编译器能够明确地知道哪些导出没有被使用。
  • CommonJS模块不直接支持Tree Shaking,因为它们是动态加载的。虽然一些现代打包工具如Webpack通过静态分析尝试实现类似Tree Shaking的效果,但可能不如ES模块那样彻底和高效。

Top-Level Await 是什么?

ES模块支持在模块顶层使用await关键字,咱们可以在脚本的最外层直接使用await关键字,等待Promise解析,而不需要将代码封装在async函数内。这在处理异步初始化或者配置加载等场景特别有用。

// example.mjs
console.log('Start');

const response = await fetch('https://juejin.cn/user/2049145406229127');
const data = await response.json();

console.log('Data received:', data);

console.log('End');

CommonJS模块不支持顶层await,在CommonJS模块中,咱们不能在模块的顶级作用域直接使用await。如果非要这样做,那就会遇到语法错误。咱们需要将await放在一个async function内部。

// example.js
console.log('Start');

(async () => {
   
    const response = await fetch('https://juejin.cn/user/2049145406229127');
    const data = await response.json();

    console.log('Data received:', data);
})();

console.log('End');

兼容性

在Node.js中使用ES模块

如果咱们在Node.js项目中使用ES模块,确保在 package.json 中设置 "type": "module",或者使用 .mjs 文件扩展名。

{
    
    "type": "module" 
}

在ES模块中使用CommonJS模块

咱们可以在ES模块中使用 import 语句来导入CommonJS模块,但需要注意模块的默认导出和命名导出的区别。

 // commonjs-module.js
 module.exports = {
    foo: "bar" };

 // es-module.js
 import commonjsModule from './commonjs-module.js';
 console.log(commonjsModule.foo); // 输出 "bar"

在CommonJS模块中使用ES模块

咱们可以在CommonJS模块中使用 import 语句,但需要使用动态导入语法

 // es-module.js
 export const foo = "bar";

 // commonjs-module.js
 (async () => {
   
     const esModule = await import('./es-module.js');
     console.log(esModule.foo); // 输出 "bar"
 })();

选择建议

  • 浏览器环境:现代JavaScript开发,优先使用ES模块,因为它们支持支持静态分析和异步加载,有助于提高性能,推荐在浏览器和现代Node.js项目中使用。
  • Node.js项目:- CommonJS模块广泛应用于Node.js生态系统,支持同步加载和动态依赖,更适合传统的Node.js项目。

注意一下,迁移现有项目从CommonJS到ES模块可能需要一定的工作量,包括修改导入导出语句、处理循环依赖问题,以及确保依赖库也支持ES模块

目录
相关文章
|
8月前
|
前端开发 芯片
【前端设计】保持代码手感——多对一的握手合并模块
【前端设计】保持代码手感——多对一的握手合并模块
【前端设计】保持代码手感——多对一的握手合并模块
|
1月前
|
前端开发 JavaScript UED
第五章(原理篇) 微前端技术之模块联邦与动态加载
第五章(原理篇) 微前端技术之模块联邦与动态加载
|
9天前
|
前端开发 JavaScript 架构师
Webpack模块联邦:微前端架构的新选择
Webpack的模块联邦是Webpack 5引入的革命性特性,革新了微前端架构。它允许独立的Web应用在运行时动态共享代码,无需传统打包过程。基本概念包括容器应用(负责加载协调)和远程应用(独立应用,可暴露模块)。实现步骤涉及容器和远程应用的`ModuleFederationPlugin`配置,以及在应用间导入和使用远程模块。模块联邦的优势在于独立开发、按需加载、版本管理和易于维护。通过实战案例展示了如何构建微前端应用,包括创建容器和远程应用,以及消费远程组件。高级用法涉及动态加载、路由集成、状态管理和错误处理。
15 3
|
9天前
|
缓存 前端开发 JavaScript
Webpack作为模块打包器,为前端项目提供了高度灵活和可配置的构建流程
【6月更文挑战第12天】本文探讨了优化TypeScript与Webpack构建性能的策略。理解Webpack的解析、构建和生成阶段是关键。优化包括:调整tsconfig.json(如关闭不必要的类型检查)和webpack.config.js选项,启用Webpack缓存,实现增量构建,代码拆分和懒加载。这些方法能提升构建速度,提高开发效率。
28 3
|
1月前
|
JSON 前端开发 搜索推荐
BoostCompass( http_server 模块 | 项目前端代码 )
BoostCompass( http_server 模块 | 项目前端代码 )
38 4
|
1月前
|
开发框架 前端开发 Android开发
【Flutter 前端技术开发专栏】Flutter 与原生模块通信机制
【4月更文挑战第30天】本文探讨了Flutter作为跨平台开发框架与原生Android和iOS交互的必要性,主要通过方法调用和事件传递实现。文中详细介绍了Flutter与Android/iOS的通信方式,数据传输(包括基本和复杂类型),性能优化,错误处理以及实际应用案例。理解并掌握这一通信机制对开发高质量移动应用至关重要,未来有望随着技术发展得到进一步优化。
【Flutter 前端技术开发专栏】Flutter 与原生模块通信机制
|
1月前
|
缓存 JavaScript 前端开发
前端 JS 经典:CommonJs 规范
前端 JS 经典:CommonJs 规范
27 0
|
1月前
|
前端开发 JavaScript 开发者
深入了解Webpack:前端模块打包工具
深入了解Webpack:前端模块打包工具
23 1
|
1月前
|
JavaScript 前端开发
前端 JS 经典:ES6 和 CommonJs 用法
前端 JS 经典:ES6 和 CommonJs 用法
24 0
|
1月前
|
前端开发 JavaScript 测试技术
前端模块规划
前端模块规划
27 1
前端模块规划