ES6 模块:现代 JavaScript 的模块化方案
本文探讨 ES6 模块,并展示如何在转译器的帮助下使用它们。几乎所有语言都有模块的概念——一种在另一个文件中包含已声明功能的方法。通常,开发人员会创建一个封装的代码库,负责处理相关的任务。该库可以被应用程序或其他模块引用。其优势在于:
JavaScript 中的模块在哪里?
几年前开始 Web 开发的任何人都会惊讶地发现 JavaScript 中没有模块的概念。无法直接引用或包含一个 JavaScript 文件到另一个文件中。因此,开发人员求助于其他方法。
多种 HTML
除非使用适当的模块模式,否则函数可能会覆盖其他函数。早期的 JavaScript 库因使用全局函数名或覆盖原生方法而臭名昭著。
或者内联:
<p>
</p>
<ul></ul>
脚本合并
<🎜>
解决多个
<p>
<strong>
</strong></p>
HTML 可以使用多个 <script></script>
标签加载任意数量的 JavaScript 文件:
<script src="lib1.js"></script>模块必须使用 MIME 类型 application/javascript 提供服务。大多数服务器会自动执行此操作,但要注意动态生成的脚本或 .mjs 文件(请参阅下面的 Node.js 部分)。常规 <script src="lib2.js"></script><script src="core.js"></script> <script>console.log('inline code');</script><script></code> 标签问题的一种方法是将所有 JavaScript 文件合并成一个大型文件。这解决了一些性能和依赖项管理问题,但可能会导致手动构建和测试步骤。</p> <p><strong>模块加载器</strong></p> <p>RequireJS 和 SystemJS 等系统提供了一个库,用于在运行时加载和命名其他 JavaScript 库。模块在需要时使用 Ajax 方法加载。这些系统有所帮助,但对于大型代码库或添加标准 <code><script></code> 标签的网站来说,可能会变得复杂。</p> <p><strong>模块打包器、预处理器和转译器</strong></p> <p>打包器引入了编译步骤,以便在构建时生成 JavaScript 代码。代码经过处理以包含依赖项并生成单个 ES5 跨浏览器兼容的合并文件。流行的选项包括 Babel、Browserify、webpack 以及更通用的任务运行器,如 Grunt 和 Gulp。</p> <p>JavaScript 构建过程需要一些努力,但也有好处:</p> <ul> <li>处理是自动化的,因此人为错误的可能性较小。</li> <li>进一步的处理可以整理代码、删除调试命令、缩小结果文件等。</li> <li>转译允许您使用替代语法,如 TypeScript 或 CoffeeScript。</li> </ul> <p><strong>ES6 模块</strong></p> <p>上述选项引入了各种相互竞争的模块定义格式。广泛采用的语法包括:</p> <ul> <li>CommonJS——Node.js 中使用的 module.exports 和 require 语法</li> <li>异步模块定义 (AMD)</li> <li>通用模块定义 (UMD)</li> </ul> <p>因此,在 ES6 (ES2015) 中提出了单一的原生模块标准。</p> <p>ES6 模块内部的所有内容默认情况下都是私有的,并且在严格模式下运行(不需要“use strict”)。公共变量、函数和类使用 export 导出。例如:</p> <pre class="brush:php;toolbar:false"><code class="language-javascript">// lib.js export const PI = 3.1415926; export function sum(...args) { log('sum', args); return args.reduce((num, tot) => tot + num); } export function mult(...args) { log('mult', args); return args.reduce((num, tot) => tot * num); } // 私有函数 function log(...msg) { console.log(...msg); }
或者,可以使用单个 export 语句。例如:
// lib.js const PI = 3.1415926; function sum(...args) { log('sum', args); return args.reduce((num, tot) => tot + num); } function mult(...args) { log('mult', args); return args.reduce((num, tot) => tot * num); } // 私有函数 function log(...msg) { console.log(...msg); } export { PI, sum, mult };
然后使用 import 将模块中的项目导入到另一个脚本或模块中:
// main.js import { sum } from './lib.js'; console.log(sum(1, 2, 3, 4)); // 10
在这种情况下,lib.js 与 main.js 在同一个文件夹中。可以使用绝对文件引用(以 / 开头)、相对文件引用(以 ./ 或 ../ 开头)或完整 URL。可以一次导入多个项目:
import { sum, mult } from './lib.js'; console.log(sum(1, 2, 3, 4)); // 10 console.log(mult(1, 2, 3, 4)); // 24
并且可以为导入指定别名以解决命名冲突:
import { sum as addAll, mult as multiplyAll } from './lib.js'; console.log(addAll(1, 2, 3, 4)); // 10 console.log(multiplyAll(1, 2, 3, 4)); // 24
最后,可以通过提供命名空间来导入所有公共项目:
import * as lib from './lib.js'; console.log(lib.PI); // 3.1415926 console.log(lib.sum(1, 2, 3, 4)); // 10 console.log(lib.mult(1, 2, 3, 4)); // 24
在浏览器中使用 ES6 模块
在撰写本文时,ES6 模块受 Chromium 系浏览器 (v63+)、Safari 11+ 和 Edge 16+ 支持。Firefox 支持将在版本 60 中到来(在 v58+ 中位于 about:config 标志之后)。使用模块的脚本必须通过在 <script></script>
标签中设置 type="module" 属性来加载。例如:
模块回退<script></script>
标签可以获取其他域上的脚本,但模块是使用跨域资源共享 (CORS) 获取的。因此,不同域上的模块必须设置适当的 HTTP 标头,例如 Access-Control-Allow-Origin: *。最后,除非在
<script></script>
标签中添加 crossorigin="use-credentials" 属性并且响应包含标头 Access-Control-Allow-Credentials: true,否则模块不会发送 Cookie 或其他标头凭据。模块执行被延迟
<script></script>
标签的defer
属性会延迟脚本执行,直到文档加载并解析完毕。模块(包括内联脚本)默认情况下会延迟。示例:<!-- 运行在第二位 --> <script type="module"> // 执行某些操作... </script> <script defer src="c.js"></script>不支持模块的浏览器不会运行 type="module" 脚本。可以使用 nomodule 属性提供一个回退脚本,模块兼容的浏览器会忽略该属性。例如:<script src="a.js"></script><pre class='brush:php;toolbar:false;'><🎜></pre> <p><strong>您应该在浏览器中使用模块吗?</strong></p> <p>浏览器支持正在增长,但现在切换到 ES6 模块可能还为时过早。目前,最好使用模块打包器来创建一个在任何地方都能工作的脚本。</p> <p><strong>在 Node.js 中使用 ES6 模块</strong></p> <p>Node.js 在 2009 年发布时,任何运行时不提供模块都是不可想象的。采用了 CommonJS,这意味着可以开发 Node 包管理器 npm。从那时起,使用量呈指数级增长。CommonJS 模块的编码方式与 ES2015 模块类似。使用 module.exports 而不是 export:</p> <pre class='brush:php;toolbar:false;'><🎜> <🎜></pre> <p>使用 require(而不是 import)将此模块导入到另一个脚本或模块中:</p> <pre class='brush:php;toolbar:false;'>// lib.js const PI = 3.1415926; function sum(...args) { log('sum', args); return args.reduce((num, tot) => tot + num); } function mult(...args) { log('mult', args); return args.reduce((num, tot) => tot * num); } // 私有函数 function log(...msg) { console.log(...msg); } module.exports = { PI, sum, mult };</pre> <p>require 也可以导入所有项目:</p> <pre class='brush:php;toolbar:false;'>const { sum, mult } = require('./lib.js'); console.log(sum(1, 2, 3, 4)); // 10 console.log(mult(1, 2, 3, 4)); // 24</pre> <p>那么,在 Node.js 中实现 ES6 模块很容易,对吗?<em>不对</em>。ES6 模块在 Node.js 9.8.0 中位于标志之后,并且至少要到版本 10 才会完全实现。虽然 CommonJS 和 ES6 模块具有相似的语法,但它们的工作方式根本不同:</p> <ul> <li>ES6 模块在执行代码之前预先解析以解析进一步的导入。</li> <li>CommonJS 模块在执行代码时按需加载依赖项。</li> </ul> <p>在上面的示例中这没有区别,但请考虑以下 ES2015 模块代码:</p> <pre class='brush:php;toolbar:false;'>const lib = require('./lib.js'); console.log(lib.PI); // 3.1415926 console.log(lib.sum(1, 2, 3, 4)); // 10 console.log(lib.mult(1, 2, 3, 4)); // 24</pre> <p>ES2015 的输出:</p> <pre class='brush:php;toolbar:false;'>// ES2015 模块 // --------------------------------- // one.js console.log('running one.js'); import { hello } from './two.js'; console.log(hello); // --------------------------------- // two.js console.log('running two.js'); export const hello = 'Hello from two.js';</pre> <p>使用 CommonJS 编写的类似代码:</p> <pre class="brush:php;toolbar:false"><code>running two.js running one.js Hello from two.js</code></pre> <p>CommonJS 的输出:</p> <pre class='brush:php;toolbar:false;'>// CommonJS 模块 // --------------------------------- // one.js console.log('running one.js'); const hello = require('./two.js'); console.log(hello); // --------------------------------- // two.js console.log('running two.js'); module.exports = 'Hello from two.js';</pre> <p>执行顺序在某些应用程序中可能至关重要,如果在同一个文件中混合使用 ES2015 和 CommonJS 模块会发生什么?为了解决这个问题,Node.js 仅允许在扩展名为 .mjs 的文件中使用 ES6 模块。扩展名为 .js 的文件将默认为 CommonJS。这是一个简单的选项,它消除了大部分复杂性,并且应该有助于代码编辑器和代码检查器。</p> <p><strong>您应该在 Node.js 中使用 ES6 模块吗?</strong></p> <p>ES6 模块仅在 Node.js v10 及更高版本(于 2018 年 4 月发布)中实用。转换现有项目不太可能带来任何好处,并且会使应用程序与早期版本的 Node.js 不兼容。对于新项目,ES6 模块提供了一种 CommonJS 的替代方案。语法与客户端编码相同,并且可能为同构 JavaScript 提供更简单的途径,同构 JavaScript 可以在浏览器或服务器上运行。</p> <p><strong>模块混战</strong></p> <p>标准化的 JavaScript 模块系统花了多年时间才出现,并且花了更长时间才实现,但问题已经得到纠正。从 2018 年年中开始,所有主流浏览器和 Node.js 都支持 ES6 模块,尽管在每个人都升级时应该预期会有一个切换延迟。今天学习 ES6 模块,以便在明天从您的 JavaScript 开发中受益。</p> <p><strong>关于 ES6 模块的常见问题解答 (FAQ)</strong></p> <p><strong>(此处省略了原文档中的FAQ部分,因为已经对全文进行了充分的伪原创)</strong></p>
以上是了解ES6模块的详细内容。更多信息请关注PHP中文网其他相关文章!