了解模块,使用JavaScript导入和导出

发布于:2020-12-19 18:52:28

0

99

0

JavaScript Web HTML CSS

在Web的早期,网站主要由HTML和CSS组成。如果将任何JavaScript加载到页面中,则通常是以小片段的形式提供效果和交互性。结果,JavaScript程序通常被完全写在一个文件中,并加载到script标签中。开发人员可以将JavaScript分解为多个文件,但是所有变量和函数仍将添加到全局范围内。

但是随着网站随着Angular,React和Vue等框架的出现而发展,并且随着公司创建高级Web应用程序而不是桌面应用程序,JavaScript现在在浏览器中扮演着重要角色。结果,迫切需要使用第三方代码来执行常见任务,将代码分解为模块化文件并避免污染全局名称空间。

在ECMAScript的2015年规范引入模块JavaScript语言,它允许使用importexport声明。在本教程中,您将学习什么是JavaScript模块以及如何使用importexport组织代码。

模块化编程

在JavaScript中出现模块概念之前,当开发人员希望将其代码组织为段时,他们将创建多个文件并将其链接为单独的脚本。为了演示这一点,请创建一个示例index.html文件和两个JavaScript文件,functions.js以及script.js

index.html文件将显示两个数字的和,差,乘积和商,并链接到script标记中的两个JavaScript文件。index.html在文本编辑器中打开并添加以下代码:

<div class="filename" "="" style="box-sizing: border-box; font-size: 0.9rem; margin-bottom: -0.8rem; padding: 0.3rem 1rem 0.5rem; line-height: 1; font-family: "Myriad Pro", sans-serif; background: linear-gradient(rgb(234, 234, 234), rgb(210, 210, 210)) rgb(255, 255, 255); border: 1px solid rgb(185, 188, 189); color: rgb(77, 73, 77); z-index: 2; margin-left: auto; margin-right: auto; text-shadow: rgba(255, 255, 255, 0.5) 0px 1px 0px; box-shadow: rgba(255, 255, 255, 0.5) 0px 1px 0px inset, rgb(81, 81, 81) 0px 1px 0px; text-align: center; border-top-left-radius: 0.3rem; border-top-right-radius: 0.3rem; white-space: normal;">index.html

<!DOCTYPE html><html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JavaScript Modules</title>
  </head>
  <body>
    <h1>Answers</h1>
    <h2><strong id="x"></strong> and <strong id="y"></strong></h2>
    <h3>Addition</h3>
    <p id="addition"></p>
    <h3>Subtraction</h3>
    <p id="subtraction"></p>
    <h3>Multiplication</h3>
    <p id="multiplication"></p>
    <h3>Division</h3>
    <p id="division"></p>
    <script src="functions.js"></script>
    <script src="script.js"></script>
  </body></html>

该HTML将在标头中显示变量的值,xyh2以下p元素中显示对这些变量的操作的值。id元素的属性是为DOM操作设置的,它将在script.js文件中发生;该文件也将设定值xy。有关HTML的更多信息,请查看我们的“如何使用HTML建立网站”系列。

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

functions.js

function sum(x, y) {
  return x + y}function difference(x, y) {
  return x - y}function product(x, y) {
  return x * y}function quotient(x, y) {
  return x / y}

最后,该script.js文件将确定和的值,x并将y函数应用于它们,并显示结果:

script.js

const x = 10const y = 5document.getElementById('x').textContent = xdocument.getElementById('y').textContent = ydocument.getElementById('addition').textContent = sum(x, y)document.getElementById('subtraction').textContent = difference(x, y)document.getElementById('multiplication').textContent = product(x, y)document.getElementById('division').textContent = quotient(x, y)

设置并保存这些文件。

对于带有一些小脚本的网站,这是一种划分代码的有效方法。但是,此方法存在一些问题,包括:

  • 污染全局名称空间:对象中已经存在您在脚本中创建的所有变量sumdifference,等)window。如果您尝试使用sum在另一个文件中调用的另一个变量,将很难知道在脚本中的任何时候都将使用哪个值,因为它们都将使用相同的window.sum变量。变量可以私有的唯一方法是将其置于函数范围内。甚至id在DOM中的x和之间可能存在冲突var x

  • 依赖性管理:必须从上到下依次加载脚本,以确保可以使用正确的变量。将脚本另存为不同的文件会产生分离的错觉,但本质上与<script>在浏览器页面中具有单个内联相同。

在ES6将本机模块添加到JavaScript语言之前,社区尝试提供几种解决方案。第一个解决方案是使用普通JavaScript编写的,例如将所有代码编写到对象或立即调用的函数表达式(IIFE)中,并将它们放在全局命名空间中的单个对象上。这是对多脚本方法的一种改进,但是仍然存在将至少一个对象放入全局名称空间的相同问题,并且没有使在第三方之间一致地共享代码的问题变得更加容易。

之后,出现了一些模块解决方案:CommonJS(一种在Node.js中实现的同步方法),异步模块定义(AMD)(一种异步方法)和通用模块定义(UMD)(一种通用的方法)支持以前两种样式的方法。

这些解决方案的出现使开发人员更易于以软件包,可分发和共享的模块(例如在npm上找到的模块)的形式共享和重用代码。但是,由于存在许多解决方案,并且都不是JavaScript固有的,因此必须实现Babel,Webpack或Browserify之类的工具才能在浏览器中使用模块。

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

模块是代码束充当一个接口到其它模块的用途,以及能够依靠其它模块的功能性的功能。模块导出以提供代码,而导入以使用其他代码。模块之所以有用,是因为它们允许开发人员重用代码,它们提供许多开发人员可以使用的稳定,一致的接口,并且它们不会污染全局名称空间。

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

本机JavaScript模块

JavaScript中的模块使用importexport关键字:

  • import:用于读取从另一个模块导出的代码。

  • export:用于向其他模块提供代码。

为了演示如何使用此功能,请将functions.js文件更新为模块并导出功能。您将export在每个功能的前面添加,这将使它们可用于任何其他模块。

将以下突出显示的代码添加到您的文件中:

functions.js

export function sum(x, y) {
  return x + y}export function difference(x, y) {
  return x - y}export function product(x, y) {
  return x * y}export function quotient(x, y) {
  return x / y}

现在,在中script.js,您将使用import来从functions.js文件顶部的模块中检索代码。

注意import必须始终位于文件的顶部,然后再包含其他路径(./在这种情况下)。

将以下突出显示的代码添加到script.js

script.js

import { sum, difference, product, quotient } from './functions.js'const x = 10const y = 5document.getElementById('x').textContent = xdocument.getElementById('y').textContent = ydocument.getElementById('addition').textContent = sum(x, y)document.getElementById('subtraction').textContent = difference(x, y)document.getElementById('multiplication').textContent = product(x, y)document.getElementById('division').textContent = quotient(x, y)

请注意,通过在花括号中命名单个函数来导入它们。

为了确保将此代码作为模块而不是常规脚本加载,请添加type="module"到中的script标记index.html。使用importexport必须使用此属性的任何代码:

index.html

<script 
  type="module" src="functions.js"></script><script 
  type="module" src="script.js"></script>

此时,您将能够使用更新来重新加载页面,并且网站现在将使用模块。浏览器支持很高,但是可以使用caniuse来检查哪些浏览器支持它。请注意,如果您将文件视为直接链接到本地文件,则将遇到此错误:

Access 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或通过托管服务提供商在Internet上进行设置。

模块在某些方面与常规脚本不同:

  • 模块不会向global(window)范围添加任何内容。

  • 模块始终处于严格模式。

  • 将同一模块两次加载到同一文件中将无效,因为模块仅执行一次/

  • 模块需要服务器环境。

模块仍然经常与捆绑程序(如Webpack)一起使用,以增加对浏览器的支持和附加功能,但它们也可以直接在浏览器中使用。

接下来,您将探索使用importandexport语法的更多方法。

命名出口

如前所述,使用export语法将允许您分别导入按其名称导出的值。以以下简化版本为例functions.js

functions.js

export function sum() {}export function difference() {}

这将允许您使用花括号导入sum并按difference名称命名:

script.js

import {sum, difference} from './functions.js'

也可以使用别名来重命名该函数。您可以这样做以避免在同一模块中命名冲突。在此示例中,sum将重命名为adddifference并将重命名为subtract

script.js

import {
  sum as add,
  difference as subtract} from './functions.js'add(1, 2) // 3

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

使用*语法,可以将整个模块的内容导入到一个对象中。在这种情况下,sum并且difference将成为对方法的mathFunctions对象。

script.js

import * as mathFunctions from './functions.js'mathFunctions.sum(1, 2) // 3mathFunctions.difference(10, 3) // 7

基本值,函数表达式和定义,异步函数,类和实例化的类都可以导出,只要它们具有标识符即可:

// Primitive valuesexport const number = 100export const string = 'string'export const undef = undefinedexport const empty = nullexport const obj = {name: 'Homer'}export const array = ['Bart', 'Lisa', 'Maggie']// Function expressionexport const sum = (x, y) => x + y// Function defintionexport function difference(x, y) {
  return x - y}// Asynchronous functionexport async function getBooks() {}// Classexport class Book {
  constructor(name, author) {
    this.name = name    this.author = author  }}// Instantiated classexport const book = new Book('Lord of the Rings', 'J. R. R. Tolkein')

所有这些出口都可以成功进口。下一节将探讨的另一种导出类型称为默认导出。

默认出口

在前面的示例中,您导出了多个命名的导出,并将它们分别导入或作为一个对象导入,每个导出都作为对象上的方法。模块也可以使用default关键字包含默认导出。默认导出不会使用大括号导入,而是直接导入到命名标识符中。

functions.js文件的以下内容为例:

functions.js

export default function sum(x, y) {
  return x + y}

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

script.js

import sum from './functions.js'sum(1, 2) // 3

这很危险,因为在导入过程中对默认导出的命名没有任何限制。在此示例中,默认功能被导入,就像difference它实际上是该sum功能一样:

script.js

import difference from './functions.js'difference(1, 2) // 3

因此,通常首选使用命名出口。与命名导出不同,默认导出不需要标识符-原始值本身或匿名函数都可以用作默认导出。以下是用作默认导出的对象的示例:

functions.js

export default {
  name: 'Lord of the Rings',
  author: 'J. R. R. Tolkein',}

您可以book通过以下方式导入它:

functions.js

import book from './functions.js'

同样,以下示例演示了如何将匿名箭头功能导出为默认导出:

functions.js

export default () => 'This function is anonymous'

这可以通过以下方式导入script.js

script.js

import anonymousFunction from './functions.js'

命名导出和默认导出可以彼此并用,如在此模块中导出两个命名值和一个默认值一样:

functions.js

export const length = 10export const width = 5export default function perimeter(x, y) {
  return 2 * (x + y)}

您可以使用以下命令导入这些变量和默认函数:

script.js

import calculatePerimeter, {length, width} from './functions.js'calculatePerimeter(length, width) // 30

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

结论

模块化编程设计实践使您可以将代码分成单独的组件,这有助于使代码可重复使用和保持一致,同时还可以保护全局名称空间。可以使用importexport关键字在本机JavaScript中实现模块接口。在本文中,您了解了JavaSvript中模块的历史记录,如何将JavaScript文件分离为多个顶级脚本,如何使用模块化方法更新这些文件,以及命名和默认导出的importandexport语法。

要了解有关JavaScript中的模块的更多信息,请阅读Mozilla开发人员网络上的模块。如果您想探索Node.js中的模块,请尝试我们的如何创建Node.js模块教程。