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

目录
相关文章
|
3月前
|
开发框架 前端开发 JavaScript
循序渐进VUE+Element 前端应用开发(15)--- 用户管理模块的处理
循序渐进VUE+Element 前端应用开发(15)--- 用户管理模块的处理
|
1月前
|
前端开发 开发者
在前端开发中,webpack 作为一个强大的模块打包工具,为我们提供了丰富的功能和扩展性
【9月更文挑战第1天】在前端开发中,Webpack 作为强大的模块打包工具,提供了丰富的功能和扩展性。本文重点介绍 DefinePlugin 插件,详细探讨其原理、功能及实际应用。DefinePlugin 可在编译过程中动态定义全局变量,适用于环境变量配置、动态加载资源、接口地址配置等场景,有助于提升代码质量和开发效率。通过具体配置示例和注意事项,帮助开发者更好地利用此插件优化项目。
65 13
|
2月前
|
前端开发 安全 测试技术
[译]一种基于模块联邦的插件前端
[译]一种基于模块联邦的插件前端
|
2月前
|
缓存 JavaScript 前端开发
|
3月前
|
前端开发 容器
前端框架与库 - Angular模块与依赖注入
【7月更文挑战第17天】探索Angular的模块化和依赖注入:模块用于组织组件、服务等,通过`@NgModule`声明。依赖注入简化类间依赖管理,但面临模块重复导入、服务作用域不当和依赖循环等问题。解决策略包括规划模块结构、正确设置服务作用域和使用工厂函数打破循环依赖。遵循最佳实践,构建高效、可维护的Angular应用。
58 17
|
2月前
|
开发框架 JSON 缓存
基于SqlSugar的开发框架循序渐进介绍(22)-- Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理
基于SqlSugar的开发框架循序渐进介绍(22)-- Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理
|
3月前
|
开发框架 前端开发 JavaScript
在微信框架模块中,基于Vue&Element前端的事件和内容的管理
在微信框架模块中,基于Vue&Element前端的事件和内容的管理
|
3月前
|
开发框架 移动开发 前端开发
在微信框架模块中,基于Vue&Element前端的后台管理功能介绍
在微信框架模块中,基于Vue&Element前端的后台管理功能介绍
|
2月前
|
前端开发 开发者
在前端开发中,webpack 作为模块打包工具,其 DefinePlugin 插件可在编译时动态定义全局变量,支持环境变量定义、配置参数动态化及条件编译等功能。
在前端开发中,webpack 作为模块打包工具,其 DefinePlugin 插件可在编译时动态定义全局变量,支持环境变量定义、配置参数动态化及条件编译等功能。本文阐述 DefinePlugin 的原理、用法及案例,包括安装配置、具体示例(如动态加载资源、配置接口地址)和注意事项,帮助开发者更好地利用此插件优化项目。
54 0
|
2月前
|
Web App开发 前端开发 JavaScript
React——前端开发中模块与组件【四】
React——前端开发中模块与组件【四】
29 0
下一篇
无影云桌面