了解 JavaScript 中的模块和导入导出语句

作者选择了 COVID-19 救援基金作为 Write for Donations计划的一部分接受捐款。

介绍

在Web的早期,网站主要由HTML(https://www.digitalocean.com/community/tutorial_series/how-to-build-a-website-with-html)和CSS(https://www.digitalocean.com/community/tutorial_series/how-to-build-a-website-with-css)组成,如果任何JavaScript都被加载到页面中,通常是以小片段的形式提供效果和互动性。

但是,随着网站随着诸如Angular(LINK0)、React(LINK1)和Vue(LINK2)等框架的发展,以及公司创建先进的Web应用程序而不是桌面应用程序,JavaScript现在在浏览器中发挥着重要作用,因此,需要使用第三方代码进行常见任务,将代码分解成模块化文件,并避免污染全球名称空间。

ECMAScript 2015 规范引入了 modules 到 JavaScript 语言中,允许使用导入导出语句. 在本教程中,您将学习 JavaScript 模块是什么以及如何使用导入导出来组织您的代码。

模块化编程

在模块概念在JavaScript中出现之前,当开发人员想要将他们的代码组织成部分时,他们会创建多个文件,并将它们链接为单独的脚本,以证明这一点,创建一个例子index.html文件和两个JavaScript文件,functions.jsscript.js

「index.html」文件會顯示兩個數字的總和、差異、產品和比值,並在「script」標籤中連結到兩個JavaScript檔案。

 1[label index.html]
 2<!DOCTYPE html>
 3<html lang="en">
 4  <head>
 5    <meta charset="utf-8" />
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 7
 8    <title>JavaScript Modules</title>
 9  </head>
10
11  <body>
12    <h1>Answers</h1>
13    <h2><strong id="x"></strong> and <strong id="y"></strong></h2>
14
15    <h3>Addition</h3>
16    <p id="addition"></p>
17
18    <h3>Subtraction</h3>
19    <p id="subtraction"></p>
20
21    <h3>Multiplication</h3>
22    <p id="multiplication"></p>
23
24    <h3>Division</h3>
25    <p id="division"></p>
26
27    <script src="functions.js"></script>
28    <script src="script.js"></script>
29  </body>
30</html>

这个HTML将显示变量x和y的值在h2的标题中,以及在以下p元素中对这些变量的操作的值。元素的id属性为 DOM 操纵,这将发生在script.js文件中;这个文件还会设置xy的值。

「functions.js」文件将包含第二个脚本中使用的数学函数. 打开「functions.js」文件并添加以下内容:

 1[label functions.js]
 2function sum(x, y) {
 3  return x + y
 4}
 5
 6function difference(x, y) {
 7  return x - y
 8}
 9
10function product(x, y) {
11  return x * y
12}
13
14function quotient(x, y) {
15  return x / y
16}

最后,script.js文件会确定xy的值,将函数应用到它们,并显示结果:

 1[label script.js]
 2
 3const x = 10
 4const y = 5
 5
 6document.getElementById('x').textContent = x
 7document.getElementById('y').textContent = y
 8
 9document.getElementById('addition').textContent = sum(x, y)
10document.getElementById('subtraction').textContent = difference(x, y)
11document.getElementById('multiplication').textContent = product(x, y)
12document.getElementById('division').textContent = quotient(x, y)

设置这些文件并保存后,您可以 打开浏览器中的index.html以显示您的网站的所有结果:

Rendered HTML with the values 10 and 5 and the results of the functions.js operations.

对于有几个小脚本的网站来说,这是一种有效的方式来分割代码,但是,有几个问题与这种方法有关,包括:

  • ** 污染全球名称空间**:你在脚本中创建的所有变量――总和差异等――现在存在于 窗口对象中。 如果你试图在另一个文件中使用另一个名为总和的变量,那么很难知道在脚本中任何时候会使用哪个值,因为它们都会使用相同的窗口变量。唯一的办法是将变量置于函数范围内。在DOM中名为xvar xid之间甚至可能存在冲突。

在 ES6 将原生模块添加到 JavaScript 语言之前,社区试图提出几种解决方案. 最初的解决方案是用瓦尼拉 JavaScript 编写的,例如在 objects立即召唤的函数表达式(IIFEs)中写下所有代码,并将它们放置在全球名称空间中的单个对象上。

之后,出现了几种模块解决方案: CommonJS,一个在 Node.js中实现的同步方法, Asynchronous Module Definition (AMD),这是一个不同步的方法,和 Universal Module Definition (UMD),这是一个支持前两种风格的通用方法。

这些解决方案的出现使开发人员更容易共享和重复使用代码以 packages 的形式,可以分发和共享的模块,如在 npm上发现的模块。

由于多文件方法的许多问题和所提出的解决方案的复杂性,开发人员对将 模块化编程方法带入 JavaScript 语言感兴趣,因此 ECMAScript 2015 支持使用 JavaScript 模块。

一个 module 是一个代码组合,作为一个接口提供其他模块的功能,以及能够依赖其他模块的功能。一个模块 exports 提供代码和 imports 使用其他代码。

模块(有时称为 ECMAScript 模块或 ES 模块)现在在 JavaScript 中可用,在本教程的其余部分中,您将探索如何在代码中使用和实现它们。

原生 JavaScript 模块

JavaScript 中的模块使用导入导出关键字:

  • import: 用于读取从另一个模块导出的代码。 _* export: 用于向其他模块提供代码。

要展示如何使用此功能,请将您的 functions.js 文件更新为模块,然后将这些功能导出。

添加以下突出的代码到您的文件:

 1[label functions.js]
 2export function sum(x, y) {
 3  return x + y
 4}
 5
 6export function difference(x, y) {
 7  return x - y
 8}
 9
10export function product(x, y) {
11  return x * y
12}
13
14export function quotient(x, y) {
15  return x / y
16}

现在,在script.js中,您将使用导入来从文件顶部的functions.js模块中获取代码。

<$>[注] :在任何其他代码之前,‘导入’必须始终位于文件的顶部,并且还必须包括相对路径(./在这种情况下)。

将以下突出代码添加到script.js:

 1[label script.js]
 2
 3import { sum, difference, product, quotient } from './functions.js'
 4
 5const x = 10
 6const y = 5
 7
 8document.getElementById('x').textContent = x
 9document.getElementById('y').textContent = y
10
11document.getElementById('addition').textContent = sum(x, y)
12document.getElementById('subtraction').textContent = difference(x, y)
13document.getElementById('multiplication').textContent = product(x, y)
14document.getElementById('division').textContent = quotient(x, y)

请注意,单个函数通过将它们命名为弯曲的轴承来导入。

为了确保此代码被加载为模块而不是常规脚本,请在index.html中添加type="module"script标签中。

1[label index.html]
2...
3<script type="module" src="functions.js"></script>
4<script type="module" src="script.js"></script>

在此时,您将能够重新加载更新页面,网站现在将使用模块。浏览器支持非常高,但 caniuse可用于检查哪些浏览器支持它。

1[secondary_label Output]
2Access to script at 'file:///Users/your_file_path/script.js' from origin 'null' has been blocked by CORS policy: Cross-origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.

由于 CORS 策略,模块必须在服务器环境中使用,您可以通过 http-server本地设置或在互联网上使用托管服务提供商。

模块在几个方面不同于常规脚本:

  • 模块不会为全球(‘窗口’)范围添加任何东西
  • 模块始终处于 严格模式
  • 将同一模块加载两次到同一文件不会产生任何影响,因为模块只执行一次
  • 模块需要服务器环境

模块仍然经常与Webpack等包一起使用,以增加浏览器支持和额外功能,但它们也可以直接在浏览器中使用。

接下来,您将探索一些可以使用进口出口语法的方法。

出口名称

如前面所示,使用导出语法将允许您单独导入以其名称导出的值。

1[label functions.js]
2export function sum() {}
3export function difference() {}

这将允许您使用弯曲的轴承以名义导入总和差异:

1[label script.js]
2import { sum, difference } from './functions.js'

还可以使用名称来重命名函数. 您可以这样做,以避免在同一模块内命名冲突. 在此示例中,‘总’将更名为‘添加’,而‘差异’将更名为‘取下’。

1[label script.js]
2import {
3  sum as add,
4  difference as subtract
5} from './functions.js'
6
7add(1, 2) // 3

在这里调用add()将产生sum()函数的结果。

使用*语法,您可以将整个模块的内容导入到一个对象中,在这种情况下,总和差异将成为mathFunctions对象上的方法。

1[label script.js]
2import * as mathFunctions from './functions.js'
3
4mathFunctions.sum(1, 2) // 3
5mathFunctions.difference(10, 3) // 7

原始值、函数表达式和定义、 非同步函数, 和实例类都可以导出,只要它们具有标识符:

 1// Primitive values
 2export const number = 100
 3export const string = 'string'
 4export const undef = undefined
 5export const empty = null
 6export const obj = { name: 'Homer' }
 7export const array = ['Bart', 'Lisa', 'Maggie']
 8
 9// Function expression
10export const sum = (x, y) => x + y
11
12// Function definition
13export function difference(x, y) {
14  return x - y
15}
16
17// Asynchronous function
18export async function getBooks() {}
19
20// Class
21export class Book {
22  constructor(name, author) {
23    this.name = name
24    this.author = author
25  }
26}
27
28// Instantiated class
29export const book = new Book('Lord of the Rings', 'J. R. R. Tolkien')

所有这些出口都可以成功导入,您将在下一节中探索的其他类型的导出称为默认导出。

缺口出口

在之前的示例中,您导出多个命名的导出,并将其单独导入或作为一个对象导入,每个导出作为对象的方法。

例如,为 functions.js 文件,请考虑以下内容:

1[label functions.js]
2export default function sum(x, y) {
3  return x + y
4}

script.js文件中,您可以将默认函数作为sum导入如下:

1[label script.js]
2import sum from './functions.js'
3
4sum(1, 2) // 3

这可能是危险的,因为在导入过程中您可以命名默认导出时没有任何限制. 在本示例中,默认函数被导入为差异,尽管实际上是函数:

1[label script.js]
2import difference from './functions.js'
3
4difference(1, 2) // 3

不同于命名出口,默认出口不需要一个标识符 - 原始值本身或匿名函数可以用作默认出口。

1[label functions.js]
2export default {
3  name: 'Lord of the Rings',
4  author: 'J. R. R. Tolkien',
5}

您可以将其导入为书籍如下:

1[label script.js]
2import book from './functions.js'

同样,下面的示例显示将匿名 箭头函数导出为默认导出:

1[label functions.js]
2export default () => 'This function is anonymous'

可以通过以下script.js导入:

1[label script.js]
2import anonymousFunction from './functions.js'

命名出口和默认出口可以一起使用,如本模块中输出两个命名值和默认值:

1[label functions.js]
2export const length = 10
3export const width = 5
4
5export default function perimeter(x, y) {
6  return 2 * (x + y)
7}

您可以通过以下方式导入这些变量和默认函数:

1[label script.js]
2import calculatePerimeter, { length, width } from './functions.js'
3
4calculatePerimeter(length, width) // 30

现在,默认值和命名值都可用于脚本。

结论

模块化编程设计实践允许您将代码分为单个组件,这有助于使您的代码可重复使用和一致,同时保护全球名称空间。

在本文中,您了解了JavaScript中的模块历史,如何将JavaScript文件分为多个顶级脚本,如何使用模块化方法更新这些文件,以及用于命名和默认导出的导入导出语法。

要了解有关 JavaScript 中的模块的更多信息,请阅读 Mozilla 开发者网络上的 Modules 如果您想探索 Node.js 中的模块,请尝试我们的 How To Create a Node.js Module tutorial

Published At
Categories with 技术
comments powered by Disqus