讲师中心 微信公众号
AI工具推荐 视频效率加速

JS如何实现模块加载?ES Module

小晨姑娘_3555

小晨姑娘_3555

发布时间:2025-08-17 08:34:02

|

875人浏览过

|

来源于php中文网

原创

ES Module是目前JavaScript模块加载的主流方案,通过import和export实现静态、标准化的模块机制,支持Tree Shaking、动态导入和代码分割,提升性能与维护性,推荐新项目优先使用。

js如何实现模块加载?es module

JavaScript实现模块加载,现在最主流且官方推荐的方式就是通过ES Module(ESM)。它彻底改变了我们组织和复用代码的方式,解决了过去全局变量污染、依赖管理混乱等一系列老大难问题。简单来说,ESM提供了一套内置的、标准化的机制来定义和使用模块,让代码结构更清晰,也更容易维护和优化。

解决方案

ES Module的核心在于

import
和
export
这两个关键词。它们是声明性的,意味着在代码执行前,模块的依赖关系就已经被确定了,这为很多编译时优化,比如Tree Shaking,打下了基础。

定义一个模块很简单,你只需要用

export
关键字把你想暴露出去的变量、函数、类等标记出来。比如:

// utils.js
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export class Calculator {
  constructor() {
    console.log('Calculator initialized');
  }
  multiply(a, b) {
    return a * b;
  }
}

// 也可以默认导出一个
export default function subtract(a, b) {
  return a - b;
}

然后,在另一个文件中,你可以用

import
来引入这些模块:

// main.js
import { PI, add, Calculator } from './utils.js'; // 命名导入
import sub from './utils.js'; // 默认导入

console.log(PI); // 3.14159
console.log(add(2, 3)); // 5

const calc = new Calculator();
console.log(calc.multiply(4, 5)); // 20

console.log(sub(10, 3)); // 7

// 动态导入:import() 返回一个 Promise,可以在运行时按需加载
document.getElementById('loadButton').addEventListener('click', async () => {
  const { add } = await import('./utils.js');
  console.log('Dynamically loaded add function:', add(10, 20));
});

在浏览器环境中,使用ES Module需要将

script
标签的
type
属性设置为
module
:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ES Module Demo</title>
</head>
<body>
    <button id="loadButton">Load More</button>
    <script type="module" src="./main.js"></script>
    <!-- 注意:type="module" 的脚本默认是 defer 的 -->
</body>
</html>

而在Node.js环境中,你需要确保文件以

.mjs
结尾,或者在
package.json
中设置
"type": "module"
,这样Node.js才会将其识别为ES Module。否则,它会默认按CommonJS规范来处理
.js
文件。

ES Module的设计理念,从一开始就考虑到了浏览器环境的异步特性。它默认就是严格模式,且模块顶层的

this
是
undefined
,这和浏览器全局环境下的
window
或CommonJS模块中的
module.exports
都不同,避免了一些意外的副作用。

ES Module与CommonJS:它们到底有什么不同,我该怎么选?

这俩兄弟,说实话,经常让人有点迷糊,尤其是在Node.js里,它们有时候还能混着用。但从根本上讲,ES Module和CommonJS(Node.js早期广泛使用的模块系统)在设计哲学和实现机制上有挺大区别。

首先是语法。ESM用的是

import
和
export
,非常直观,而且是语言层面的标准。CommonJS则是
require()
和
module.exports
(或者
exports
),这是Node.js运行时提供的一套API。

再来是加载机制。ESM是静态的,这意味着在代码执行前,模块的依赖关系就已经确定了。你可以想象成,在编译阶段,或者说在浏览器解析

<script type="module">
的时候,它就知道哪个模块依赖哪个模块。这带来了很大的好处,比如前面提到的Tree Shaking。而CommonJS是动态的,
require()
是一个函数调用,它可以在代码运行的任何时候、任何地方被调用,甚至可以根据条件来加载不同的模块。这种运行时加载的特性,让CommonJS在某些场景下显得更灵活,但同时也牺牲了一些优化空间。

一个很关键的区别是值的导出与导入。CommonJS导出的是值的拷贝。当你

require
一个模块时,你得到的是它
module.exports
在那个时间点的一个快照。如果原模块内部后面改变了某个导出的值,你导入的那个值是不会跟着变的。但ESM是值的引用(live binding)。如果你导入了一个模块里的变量,而这个变量在原模块里被修改了,你导入的那个变量也会跟着变。这在处理一些状态共享的场景时,需要特别注意。

还有就是

this
的指向。在ESM模块的顶层,
this
是
undefined
。而在CommonJS模块中,
this
默认指向
module.exports
。这虽然是个小细节,但有时候会导致一些意外行为。

至于怎么选?我的建议是,新项目,尤其涉及浏览器前端的,无脑选ES Module。它是未来,是标准,有更好的生态工具支持(比如Webpack、Rollup的Tree Shaking),也更符合现代JavaScript的开发范式。如果你在写纯Node.js的后端服务,并且不需要考虑浏览器兼容性,或者是在维护一个老项目,CommonJS依然是可行的,因为它在Node.js社区有深厚的积累。当然,Node.js现在也完全支持ESM,甚至鼓励大家迁移。现在很多项目都是用构建工具把ESM代码打包成CommonJS或者反过来,所以实际开发中,你可能同时会接触到它们。

在实际项目中,ES Module的异步加载机制会带来哪些便利和挑战?

ES Module在浏览器环境中是默认异步加载的,这和

<script defer>
或者
<script async>
有点像,但又不仅仅是这样。它的便利性是显而易见的:

便利性:

  • 非阻塞渲染: 模块脚本的加载和执行不会阻塞HTML的解析和渲染。这意味着用户可以更快地看到页面内容,提升了用户体验。想象一下,如果你的JS模块很大,同步加载会卡住整个页面,那体验会多糟糕。
  • 并行加载: 浏览器可以同时请求多个模块文件,只要它们之间没有直接的依赖关系,或者依赖关系解析清楚后,就能并行下载。这大大加快了大型应用的启动速度。
  • 按需加载(Dynamic Import): 通过
    import()
    语法,我们可以在运行时动态地、条件性地加载模块。这简直是前端性能优化的利器。比如,一个用户只有点击某个按钮才需要某个复杂组件的代码,我们就可以在点击时才去加载它,而不是在页面初始化时就全部加载进来。这也就是常说的代码分割(Code Splitting)和懒加载(Lazy Loading)。
  • 更好的资源管理: 因为是异步的,所以浏览器可以更智能地管理资源优先级,优化网络请求。

挑战:

  • 依赖管理和构建工具的依赖: 尽管ESM本身是异步的,但在大型项目中,手动管理所有模块的依赖关系,确保它们按正确顺序加载,会变得非常复杂。这时候,像Webpack、Rollup、Parcel这样的构建工具就成了必需品。它们负责解析模块依赖图、打包、优化,把多个模块合并成少数几个文件,以减少HTTP请求。
  • 开发环境和生产环境的差异: 在开发时,你可能直接用
    type="module"
    ,浏览器会单独请求每个模块文件。但在生产环境,为了性能,我们通常会把这些模块打包成一个或几个文件。这种差异需要构建工具来抹平。
  • 循环依赖(Circular Dependencies): 虽然ESM的live binding机制在一定程度上缓解了循环依赖的问题(至少不会像CommonJS那样直接报错),但它依然是一个设计上的“臭味”。如果两个模块A和B互相导入对方,并且在初始化时就依赖对方的导出,可能会导致某些值在被使用时还是
    undefined
    。这需要开发者在模块设计时就尽量避免。
  • 旧浏览器兼容性: 如果你的项目需要支持IE或者一些很老的浏览器,那么ESM是无法直接运行的。你需要使用Babel这样的转译工具,将ESM语法转换成旧浏览器能理解的CommonJS或其他格式。

说白了,ESM的异步加载机制,给了我们巨大的优化空间,但同时也把一部分复杂度推给了构建工具和开发者本身。但我觉得这笔交易是划算的,因为带来的性能提升和开发体验改善是实实在在的。

如何利用ES Module的特性进行代码优化,比如Tree Shaking?

ES Module的静态结构特性,是实现很多高级代码优化的基石,其中最亮眼的莫过于Tree Shaking。

Tree Shaking(摇树优化):

这个名字很形象,就像摇晃一棵树,把上面枯死的、没用的叶子(代码)摇下来,只留下有用的部分。

  • 工作原理: 正如前面提到的,ESM的
    import
    和
    export
    是静态的。这意味着在代码执行前,工具就能分析出模块之间的确切依赖关系。当构建工具(如Webpack 4+、Rollup、Parcel)处理ESM时,它们会遍历你的代码,识别出哪些
    export
    被实际
    import
    并使用了,哪些则根本没有被引用。那些没有被引用的代码,就会在最终的打包文件中被移除掉。
  • 为什么ESM能做到? CommonJS的
    require()
    是动态的,你可以在运行时根据条件加载,工具无法在编译时确定所有可能的依赖。比如
    const myModule = require(someCondition ? './moduleA' : './moduleB');
    ,构建工具就很难判断
    moduleA
    和
    moduleB
    哪个会被用到。但ESM的
    import
    语法是固定的,工具可以清晰地构建出依赖图。
  • 实际效果: 极大地减小了最终的JavaScript包体积。想象一下,你可能只用了某个大型UI库里的一两个组件,或者某个工具库里的一两个函数,Tree Shaking能确保只有你用到的那部分代码才会被打包进去,而不是整个库。这对于首次加载速度至关重要。
  • 实现条件: 要让Tree Shaking有效,除了使用ESM外,还需要注意:
    • 模块无副作用(side-effect free): 如果一个模块在被导入时会执行一些全局操作(比如修改
      window
      对象),即使它的导出没有被使用,也不能被简单地移除。通常,你需要在
      package.json
      里通过
      "sideEffects": false
      来告诉打包工具这个模块是纯净的,可以安全地进行Tree Shaking。
    • 构建工具支持: 确保你的构建工具配置了Tree Shaking(现代工具通常默认开启)。

其他基于ESM的优化:

  • 代码分割(Code Splitting)和懒加载(Lazy Loading): 这是Tree Shaking的孪生兄弟,都是为了减小初始加载体积。利用
    import()
    动态导入语法,你可以将应用代码分割成多个块,只在需要时才加载它们。例如,一个管理后台,只有管理员登录后才需要加载管理员专属的功能模块。
  • 更好的缓存策略: 因为代码被分割成更小的、独立的块,当某个模块内容更新时,只有对应的代码块需要重新下载,其他未改变的模块可以继续使用浏览器缓存,进一步提升二次加载速度。
  • 更清晰的依赖图: ES Module的静态导入让依赖关系一目了然,这不仅对构建工具友好,对开发者理解项目结构、排查问题也大有裨益。当你看一个文件的
    import
    语句时,你就知道它依赖了什么,这比CommonJS那种散落在文件各处的
    require
    要清晰得多。

总的来说,ES Module不仅仅是一种新的模块化语法,它更是一种现代JavaScript应用架构和性能优化的基础。理解并善用它的特性,能让你的项目在性能和可维护性上都迈上一个新台阶。

热门AI工具

更多
SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

1975

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

2722

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

936

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

3059

2025.09.10

require的用法
require的用法

require的用法有引入模块、导入类或方法、执行特定任务。想了解更多require的相关内容,可以阅读本专题下面的文章。

1935

2023.11.27

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

1958

2023.09.20

全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

3805

2025.09.18

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

1570

2025.09.18

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

160

2026.09.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
phpMyAdmin 常见问题
phpMyAdmin 常见问题

共0课时 | 0人学习

uni-app快速上手
uni-app快速上手

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn