前端小白也能懂:ES模块和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 / exportrequire / 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模块

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值