资讯动态

深入理解 ES6 模块(ESM):从全局作用域困境到 import/export 模块化编程

发布时间:2026/9/15 15:20:10 来源:尧图企业网站定制
深入理解 ES6 模块ESM从全局作用域困境到 import/export 模块化编程【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum本篇文章以 The Odin Project 开放课程本仓库中 ES6 模块课程 为主体系统讲解 ES6 模块ESMES2015 时代引入的官方模块系统的诞生背景、核心语法命名导出/导入、默认导出/导入与入口点/依赖图机制并对比其与 CommonJS 的差异。读完本文你将能够用import/export组织多文件 JavaScript 项目理解浏览器加载模块脚本的原理并为后续学习 Webpack 打包与 npm 依赖管理打下坚实基础。为什么需要真正的模块从全局作用域困境说起在 ES6又称 ES2015发布之前JavaScript 没有官方的模块系统。此前课程中学习的模块模式module pattern虽然能帮助组织变量和函数但面对更复杂的项目时文件会越来越长——如果能把代码拆分到多个文件就好了。使用多个文件正是模块要解决的核心问题。在此之前模块模式基于 IIFE曾扮演重要角色而 ES6 带来的真正的模块通常被称为ES6 modules或ESMECMAScript Modules。多个script标签共享同一个全局作用域假设我们有两个脚本one.js和two.js并在 HTML 中以独立脚本链接script srcone.js defer/script script srctwo.js defer/script// one.js const greeting Hello, Odinite!;// two.js console.log(greeting);打开 HTML 后控制台会输出Hello, Odinite!——尽管greeting从未在two.js中定义原因是两个脚本被依次加载到同一个全局作用域中效果等同于把两行代码按顺序写在一个文件里。如果调换顺序、把two.js的script标签放在前面则会报错greeting is not defined因为它在变量定义之前就尝试输出了。这意味着即使使用多个 JavaScript 文件它们仍然共享全局作用域顶层变量并不安全。这正是拆分文件的拦路虎。IIFE 与模块模式ESM 之前的变通方案在 ESM 之前可以把代码包进IIFEImmediately Invoked Function Expression立即调用函数表达式中代码照常运行但内部变量被限定在函数作用域内而不再全局可见// one.js (() { const greeting Hello, Odinite!; })();此时控制台会报greeting is not defined因为全局中不再存在名为greeting的变量。但如果我们只想让部分内容对其他文件可见呢可以借助 IIFE 的返回值把指定内容暴露到全局其余保持私有// one.js const greeting (() { const greetingString Hello, Odinite!; const farewellString Bye bye, Odinite!; return greetingString; })();现在全局变量greeting的值是Hello, Odinite!two.js中的代码可以成功输出它而greetingString与farewellString都是 IIFE 内部的局部变量——greetingString被显式返回并赋值给greeting所以在函数外部可访问farewellString未被返回在two.js中完全不可访问。通过这种方式我们能够自主选择从一个文件向后续所有文件暴露什么内容。这正是 IIFE 常被称为模块模式的原因在真正的模块出现之前它让我们能够在多个文件之间编写模块化代码。值得一提的是本仓库 工厂函数与模块模式课程 对该模式有更深入的讲解——利用闭包隐藏私有状态再通过返回对象对外暴露指定接口实现封装encapsulation。例如其中的计算器示例用 IIFE 包裹工厂函数将lastResult作为私有变量隐藏仅暴露add、subtract、getLastResult等方法。一个关于语法的说明课程中即便是在前 ES6示例里也会使用let/const和箭头函数。这不会改变全局作用域与模块模式的工作方式它们才是本部分的核心只是为了让示例更贴近现代写法。ES6 模块每个文件默认拥有私有作用域有了 ESM我们对代码的控制力大大增强。每个文件默认拥有自己的私有作用域不仅能选择从该文件导出什么还能选择向其他文件导入什么。也就是说导出了某个东西并不意味着它自动对一切可见——只有在我们显式导入它的文件中才可用。控制力十足模块作用域不是全局作用域使用 ESM 时每个模块都有独立的私有作用域模块之间通过 import/export 通信。模块中的顶层变量在全局作用域中不可访问。Import 与 Export两种导出方式的完整指南与 JavaScript 一贯的风格一致导入/导出也有两种类型默认导出default与命名导出named。它们做的事情本质相同但语法略有差异且可以在同一文件中混用。以下先展示不同语法此时代码还不能运行因为还需要把脚本以 ES6 模块方式链接到 HTML这一点会在入口点一节说明。命名导出Named exports以one.js和two.js为例先在one.js中导出问候与告别字符串。命名导出有两种写法在声明前加上export关键字或在文件某处通常结尾添加export { }花括号内列出要导出的名字。两种方式都合法命名导出的数量不限。// one.js export const greeting Hello, Odinite!; export const farewell Bye bye, Odinite!;或者单独一行// one.js const greeting Hello, Odinite!; const farewell Bye bye, Odinite!; export { greeting, farewell };然后在two.js中导入这些变量。记住导入是可控的如果只需要greeting可以只导入它如果其他文件需要farewell或两者就按需导入。不导入就不能使用。命名导入需要在{ }中写明要导入的名字并提供来源文件的路径将来导入第三方库时可以直接用库名代替完整文件路径。注意文件路径不能用模板字符串只能用单引号或双引号字符串。// two.js import { greeting, farewell } from ./one.js; console.log(greeting); // Hello, Odinite! console.log(farewell); // Bye bye, Odinite!⚠️ 命名导入/导出不是对象字面量export { greeting, farewell }; import { greeting, farewell } from ./one.js;上面的代码既不是导出包含greeting、farewell键的对象也不是对对象进行解构赋值。{ }在命名导入/导出中是一种特殊语法与对象字面量、对象解构完全无关。默认导出Default exports与命名导出不同一个文件只能默认导出一个东西。默认导出的内容不附带名字因此在别处导入时你可以自行决定给它取什么名字。默认导出同样支持两种写法在声明前加上export default内联导出或在文件末尾这次不加花括号导出。注意如果要内联默认导出变量default关键字会取代变量声明即直接导出该表达式。将one.js中的问候字符串默认导出// one.js export default Hello, Odinite!;或者单独一行// one.js const greeting Hello, Odinite!; export default greeting;在two.js中进行默认导入。因为导入的是默认导出的内容名字可以随意取——one.js中变量叫greetingtwo.js中完全可以不叫这个名字。默认导入不使用花括号花括号专属于命名导入// two.js import helloOdinite from ./one.js; console.log(helloOdinite); // Hello, Odinite!默认导出与命名导出混用同一文件中可以同时使用默认导出和命名导出。关于何时使用哪种业界其实没有统一标准——唯一明确的事实是一个文件可以有多个命名导出但只能有一个默认导出。当模块只需导出一个东西时有人偏好默认导出有人偏好单个命名导出两者都可以团队协作时遵循团队约定即可。先默认导出问候字符串再把告别字符串作为命名导出// one.js export default Hello, Odinite!; export const farewell Bye bye, Odinite!;在two.js中同时导入两者——默认导入问候字符串名字随意命名导入告别字符串// two.js import greeting, { farewell } from ./one.js; console.log(greeting); // Hello, Odinite! console.log(farewell); // Bye bye, Odinite!入口点Entry point一个script标签管理整个依赖图使用 ESM 时无需再按顺序把每个 JavaScript 文件都加进 HTML只需链接一个文件——入口点entry pointscript srctwo.js typemodule/script为什么two.js是入口点因为上述示例中two.js从one.js导入变量即two.js依赖one.js形成如下依赖图dependency graphimporter depends on exporter two.js -------------- one.js当浏览器把two.js作为模块加载时会发现它依赖one.js于是把one.js的代码也一并加载。反之如果以one.js为入口点浏览器会看到它不依赖任何其他文件于是不再做任何事——two.js的代码永远不会执行什么都不会被输出。再举两个更复杂的依赖关系示例。假如存在three.js导出内容且two.js从中导入two.js仍是入口点现在它同时依赖one.js与three.jstwo.js -------------- one.js └------- three.js或者反过来one.js从three.js导入。此时two.js依然是入口点并通过one.js间接依赖three.jstwo.js -------------- one.js -------------- three.js注意我们只需一个script标签额外的文件依赖由浏览器自动处理也不需要加defer属性因为typemodule会自动延迟脚本执行。动手练习如果你跟着本课开头的 IIFE 示例写过代码可以尝试把 JavaScript 改写成import/export写法并只把入口点以模块脚本方式链接。由于浏览器安全限制直接双击 HTML 文件无法加载 ES6 模块请务必使用开发服务器例如 VS Code 的 Live Preview 扩展运行页面。仓库实践印证入口点与依赖图也是打包器的核心概念本仓库 Webpack 课程 明确说明打包器bundler复用与 ESM 完全相同的入口点 依赖图概念——你提供入口点打包器从该文件构建依赖图把所有相关文件合并最终输出一个包含全部必要代码的单一文件。也就是说本文讲解的入口点与依赖图不仅是浏览器原生 ESM 的机制也是后续学习 Webpack 配置entry、output等字段的前提。此外该课程还指出一个实用的细节原生 ESM 中导入本地文件必须写清扩展名如./greeting.js而 Webpack 等打包器允许省略.js扩展名——这是打包器的特性而非 ESM 本身本仓库的课程示例中会尽量保留扩展名以保持明确。CommonJSNode.js 世界的另一套模块系统学习途中你可能碰到过CommonJSCJS它使用require和module.exports而非import/export。在 Foundations 课程的 JavaScript 练习中你可能见过这种写法。CJS 是为 Node.js 设计的模块系统工作方式与 ESM 略有不同而且浏览器无法直接理解它。CJS 目前在 Node.js 代码中仍被大量使用不过近年来 Node.js 对 ESM 的支持越来越流行。本课程阶段聚焦于浏览器端运行代码所以使用 ESM如果走 Full Stack JavaScript 学习路线后续的 Node.js 课程会详细介绍 CJS。模块化在课程项目中的落地理解 ESM 后可以看看本仓库的课程项目如何把模块化思想落到实战餐厅主页项目要求每个标签页Home/Menu/Contact的内容放在独立的模块中每个模块导出一个创建div元素并追加到 DOM 的函数标签切换逻辑写在index.js入口模块中通过事件监听器清空div#content并重新调用对应标签模块。这正是每个模块导出函数、入口模块统筹调度的典型实践。Todo List 项目明确要求将应用逻辑创建 todo、标记完成、调整优先级等与DOM 相关代码分离分别放进独立模块。npm 课程 进一步说明随着项目变大自己写的文件与第三方包越来越多依赖管理会变得棘手且向浏览器发送大量 JS 文件会影响性能——这正是下一课引入打包器的原因。本仓库根目录的 package.json 就是实际范例npm 读取它来安装markdownlint-cli2等开发依赖并执行lint/fix/test脚本。这些课程共同构成了从ESM 语法到模块化工程组织再到打包与依赖管理的完整学习链条。知识检查回顾本课要点检验自己的掌握程度在 ES6 模块出现之前如何让一个变量对其他文件不可访问私有化提示IIFE 与函数作用域在 ES6 模块出现之前如何把变量暴露给后续文件使用提示从 IIFE 返回并赋值给全局变量编写模块化代码有哪些好处拆分长文件、独立作用域、按需导入、易于复用与维护默认导出与命名导出的区别是什么数量限制、语法、导入时的命名自由度什么是入口点唯一需要链接到 HTML 的模块脚本浏览器据此加载整个依赖图如何在 HTML 中链接模块脚本script src... typemodule/script无需defer【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价