在 Construct 中学习 JavaScript,第 12 部分:模块

566次阅读
没有评论

共计 5435 个字符,预计需要花费 14 分钟才能阅读完成。

在 Construct 中学习 JavaScript,第 12 部分:模块

这是 “在 Construct 中学习 JavaScript” 教程系列的第 12 部分。本部分是第 11 部分的延续。如果你错过了第 11 部分,请查看“在 Construct 中学习 JavaScript,第 11 部分:Construct API”

在本指南中,我们已经涵盖了 JavaScript 语言的许多基础知识,以及该语言、浏览器和 Construct 中的一系列内置功能。现在我们即将结束本指南,只剩下最后一个主要主题:模块。在这里,我们可能会涉及很多内容,但我们将介绍这个概念,涵盖基础知识,并描述一些 Construct 特有的方面。特别是,由于事件表中的脚本是 Construct 特有的概念,因此有特定的处理方式。

#  在更广泛的 JavaScript 生态系统中,有多种不同的方法来处理模块。然而,在本指南中,我们将处理内置于 JavaScript 语言中的模块系统,有时也称为 ES 模块。大多数其他模块系统都早于它,并且从长远来看,更广泛的 JavaScript 生态系统可能会趋向于在任何地方都使用 ES 模块。

模块

请记住,顶级变量和函数的作用域是文件。这意味着你可以在该脚本文件的任何地方使用它们,但不能在另一个脚本文件中使用。

跨文件使用事物的一种方法是将它们设置为全局变量,将它们添加为 globalThis 的属性。然而,这有许多问题。它很快就会变得混乱,因为你最终可能会在一个地方有数百甚至数千个函数。此外,它可能会导致名称冲突:如果你想使用两个不同的库,并且它们都试图定义一个具有相同名称的全局函数,一个将覆盖另一个,并且可能会破坏事物。

模块通过为在 JavaScript 文件之间共享事物提供一个干净和有组织的接口来解决这些问题。每个文件都是一个自包含的模块 – 其顶级变量和函数不能在文件外部访问。换句话说,它的内容是模块私有的。如果你想将某些内容暴露给文件外部使用,必须使用 export 关键字导出。然后,另一个文件必须使用 import 关键字导入它。

# 在这一点上,这可能感觉有些牵强,因为到目前为止我们的所有示例都涉及少量的变量和函数。为什么你需要将它们分布在不同的文件中?对于简单的代码,确实很容易将所有内容都放在一个文件中。然而,当你开始编写更多的代码时,将其组织在多个文件中很快就变得非常重要。否则,你可能会得到混乱的代码,难以添加、更改或修复。理解模块将有助于确保你在开始编写大量代码时能够保持代码的组织性。它对于重用代码和集成可能作为模块工作的库也很有用。

一个简单的导出

对于这个示例,让我们使用一个新的空项目,其中包含两个脚本文件:main.js 和 mymodule.js。我们将从 mymodule.js 中导出一些内容,并在 main.js 中使用它。下载并打开下面的项目文件以获取此示例。

在 Construct 中学习 JavaScript,第 12 部分:模块
Download now

打开文件 mymodule.js。添加下面的函数,该函数仅返回一个字符串。

在 Construct 中学习 JavaScript,第 12 部分:模块

目前,此函数无法在该文件外部使用。但是,如果我们添加 export 关键字,它将可供其他文件导入。这放在函数的开头,如下所示。

在 Construct 中学习 JavaScript,第 12 部分:模块

脚本文件可以导出任意数量的内容。在这种情况下,我们只导出一个函数。

现在打开 main.js。我们想要调用 GetMessage(),但它仍然不可用。要从 mymodule.js 导入所有内容,请添加以下行:

在 Construct 中学习 JavaScript,第 12 部分:模块

*表示“所有内容”,as MyModule 表示将所有内容放入名为 MyModule 的对象中,from "./mymodule.js" 表示要加载的文件。因此,总的来说,这意味着“从 mymodule.js 导入所有内容到名为 MyModule 的对象中”。

# 请注意,文件路径 **"./mymodule.js"** 以 **./** 开头。这实际上是必需的,并表示它是一个文件(因为可以从其他位置或以其他方式导入事物)。

现在,从 mymodule.js 导出的所有内容都在名为 MyModule 的对象中。因此,我们的 GetMessage()函数可以通过 MyModule.GetMessage()调用。尝试在 main.js 中使用以下代码将消息记录到控制台。

在 Construct 中学习 JavaScript,第 12 部分:模块

预览项目并检查浏览器控制台,你应该看到消息出现!

恭喜 – 你已经成功完成了第一次导入。

额外的几点关于模块工作方式的说明

以下是关于模块工作方式的一些额外说明。

主脚本

Construct 本身只会加载并运行 main.js。如果你在项目栏中选择脚本文件,你会在属性栏中看到一些属性出现。这些属性包括用途,main.js 的用途设置为“主脚本”。

一个项目只能有一个主脚本。所有其他脚本如果要被加载和使用,都必须被导入,因为 Construct 不会自动加载它们。(注意,mymodule.js 的用途显示为“未设置”。)

主脚本在项目栏中也会被高亮显示,这样你就可以很容易地看到设置为主脚本的是哪一个。

重复导入相同的文件

如果你有多个文件都导入了 mymodule.js,它仍然只会加载和运行该脚本一次。第一次加载时实际上会加载它,所有后续的加载都会重复使用第一次加载的版本。这确保了模块高效运行。

导入的名称

请注意,从 mymodule.js 导出的内容被导入到了名为 MyModule 的对象中。这避免了将所有内容都设置为全局或顶级函数,从而避免了之前提到的各种问题。它保持了所有内容的组织,并意味着你可以轻松地从两个不同模块中使用具有相同名称的两个函数。

在编程中,术语“命名空间”指的是使用名称的位置,例如脚本文件的顶级,或者在 MyModule 这样的对象中。

将内容保持在不同的命名空间中可以避免问题,比如两个具有相同名称的函数互相覆盖。

你可以以某种方式将内容导入到顶级作用域中,甚至可以使用不同的名称导入它们。但是,由于你可以根据自己的喜好编写导入语句,你始终可以控制你导入的所有内容使用的名称。

导入和导出是静态的

导出只能出现在脚本的顶级。你不能在 ‘if’ 语句内部导出一些内容。这确保了模块必须始终导出相同的集合。

导入语句必须出现在脚本的顶部,任何其他代码行之前。这也意味着你不能在 ‘if’ 语句内部使用导入语句(尽管存在一种用于这种用途的动态导入特例)。

这些规则旨在允许工具能够做诸如捆绑和优化代码这样的事情。它也有助于保持事物的一致性和可预测性。

导入和导出

以下是关于 importexport 关键字的一些额外内容。这不是全面的总结,但提供了有用的概述。

导入特定函数

以我们之前的例子为基础,GetMessage() 函数也可以这样导入和使用:

在 Construct 中学习 JavaScript,第 12 部分:模块

在这个例子中,import {GetMessage} from "./mymodule.js"; 的意思是只导入从 mymodule.js 导出的 GetMessage 函数。它也成为了一个顶级函数,因此可以直接使用 GetMessage() 调用;这里没有名为 MyModule 的对象。

可以使用以下形式导入多个内容:import {FuncA, FuncB, FuncC} from "./file.js";

原始示例导入了所有内容并给它命名。这种方法在你知道只需要导入某些特定内容而不需要全部内容时非常有用。

仅导入脚本以运行

你也可以使用 import "./file.js"; 仅加载并运行一个脚本文件。它不会导入任何内容或以任何方式使用其导出。

这对于一些旧库非常有用,这些库不导出任何内容,而是添加全局变量。在这种情况下,导入脚本后,你可以访问它添加到 globalThis 的任何内容。或者,可能脚本通过运行其顶级代码执行一些有用的操作,这种情况下你不需要导入任何内容,因为仅加载和运行脚本就足够了。

默认导出

如果一个脚本只有一个导出,或者有一个主要的内容你大多数时间想要导入,那么可以将其设置为默认导出。这意味着它是用 export default 导出的,而不是简单的 export。尝试以下代码用于 mymodule.js

在 Construct 中学习 JavaScript,第 12 部分:模块

默认导出的导入方式略有不同。尝试以下代码用于 main.js

在 Construct 中学习 JavaScript,第 12 部分:模块

现在预览项目,消息将被记录。

在这种情况下,import Thing from "./file.js" 的意思是导入来自 file.js 的默认导出,并将其命名为 Thing。注意,这里没有使用大括号 {}(在之前的例子中导入了具有特定名称的导出),也没有使用 *as 部分(导入所有内容)。

不要忘记,以这种方式导入的脚本必须指定一个默认导出,否则你会得到一个错误。

导入是只读的

你导入的所有内容都是只读的,即不能重新赋值。这有助于确保模块始终暴露一致的接口。

然而,有时这可能会造成一些问题。例如,如果你想导出一个变量,如下所示:

在 Construct 中学习 JavaScript,第 12 部分:模块

并像这样导入它:

在 Construct 中学习 JavaScript,第 12 部分:模块

… 那么在 导入 后,你无法更改 myVariable 的值。尽管我们使用 let 声明了变量,但它仍然表现得像是 const

解决这个问题的一种方法是使用对象。对象本身不能被重新赋值,但其属性可以。因此,我们可以使用对象属性而不是变量。以下是一个示例,展示如何将相关变量组存储在一个模块中。在 mymodule.js 中使用:

在 Construct 中学习 JavaScript,第 12 部分:模块

这使用了一个默认导出的对象,具有 score、lives 和 ammo 的属性。

然后在 main.js 中使用:

在 Construct 中学习 JavaScript,第 12 部分:模块

这将默认导出导入为 GameVariables。该对象本身不可重新赋值,但这并不重要:它的属性可以被更改,因此 GameVariables.score += 100 按预期工作,最终得分为 200。

这是一种管理相关变量集作为独立模块的有用技术。例如,你可以将其用于“全局”变量——这些变量实际上并不在全局作用域中(因为它们实际上不在全局对象上),但提供了一种更有组织的方式来在所有代码中共享一组变量。

可以导出的内容

你几乎可以从脚本文件中导出任何内容。然而,由于导入的任何内容都是只读的(即不可重新赋值),导出变量并不是很有用,因为它们总是表现得像是用 const 声明的。通常,模块导出对象、函数和类,因为这些是最有用的共享内容。

导入事件中的事件

之前我们使用脚本在事件表中。在这里使用导入需要额外的步骤。问题在于事件表中脚本的代码实际上在函数内部运行,而 导入 导出 语句不允许在函数内部。因此,需要另一种方法来利用事件表中脚本的导入。

事件表中的脚本不支持导出 – 你必须使用脚本文件。然而,使用一个特殊文件支持导入,该文件的目的是事件的导入。简而言之,导入在此特殊文件中的所有内容都可以在事件表中的所有脚本中使用。

下载并打开下面的项目以查看示例。

在 Construct 中学习 JavaScript,第 12 部分:模块
Download now

在这个示例中,我们有以下内容:

  • 在 main.js 中,导出了一个名为 GetMessage 的函数,它只是返回一个带有消息的字符串。(主脚本仍然可以导出内容并被其他地方导入。)
  • 还有一个名为 importsForEvents.js 的脚本文件,其 Purpose 属性设置为事件的导入。在此特殊脚本文件中导入的内容可以在事件表中的脚本中使用。此文件从 main.js 导入 GetMessage。因此,现在可以在事件表中的所有脚本中使用 GetMessage。
  • 在事件表 1 中,一个脚本在“布局启动时”运行,将 GetMessage() 打印到控制台。预览项目,您将在控制台中看到消息出现。

而不是调用一个函数来获取值,您也可以使用一个函数来执行一些工作。例如,如果您有一个长函数包含大量代码,通常更方便将它放在一个脚本文件中,并从事件表中的脚本调用该函数。这与上述示例完全相同,只是 GetMessage 函数会执行其他操作(并被调用其他名称)。

使用全局变量是另一种方法,因为 globalThis 在脚本文件和事件表中的脚本中都是可访问的。但是,如前所述,使用全局变量有多个陷阱,因此使用模块的方式更优。

另一种有用的方法是在 importsForEvents.js 中:

在 Construct 中学习 JavaScript,第 12 部分:模块

然后,事件表中的脚本可以调用 main.js 中导出的任何函数,例如 Main.GetMessage()。在较大的项目中,您可以重复此操作,从事件表中的脚本轻松调用来自不同脚本文件的函数。

事件表是构造的独特概念,因此使用模块在事件表中暴露函数的方式是构造自身的设计的一部分。在处理事件表中的脚本时,通常没有太多空间输入(尤其是在动作部分,左边被条件占据),并且通常有很多事件。因此,一个良好的风格是将任何长代码块写为脚本文件中的函数,并通过事件表中的脚本导入这些函数。这使事件表中的 JavaScript 代码保持简短和简单,并使调用其他函数和导入脚本文件中的内容变得容易。

结论

在这部分中,我们覆盖了:

  • 每个脚本文件都是一个模块,默认情况下保持其内容私有。
  • 使用 export 来在脚本文件之外共享内容。
  • 使用 import 来使用另一个脚本文件的导出。
  • 构造只加载主脚本,其他脚本必须被导入。
  • 导入 导出 的其他使用方式,包括默认导出。
  • 导出是只读的,以及使用对象属性而不是变量进行导出的好处。– 使用“事件导入”脚本从事件表中的脚本调用脚本文件中的函数。

学习更多

我们只涵盖了 导入 导出 的一些使用方式。它们还有更多使用方式,既可以导入也可以导出默认项目和命名项目,使用子集项目,重命名项目等。有关更多详细信息,请参阅 MDN Web Docs 上的这些链接。

  • export 语句
  • import 语句(也涵盖动态导入)
  • MDN 关于 JavaScript 模块 的章节提供了有关使用模块的更多详细信息
  • 幽灵射击代码 示例展示了使用模块在脚本文件之间共享内容的完整 JavaScript 编码版本的“幽灵射击”示例。

下一部分

当您准备好继续学习时,请前往本指南的最终部分:在构造中学习 JavaScript,第 13 部分:继续前进!

正文完
 0
评论(没有评论)