发布于:2020-12-19 18:52:28
0
99
0
在Web的早期,网站主要由HTML和CSS组成。如果将任何JavaScript加载到页面中,则通常是以小片段的形式提供效果和交互性。结果,JavaScript程序通常被完全写在一个文件中,并加载到script
标签中。开发人员可以将JavaScript分解为多个文件,但是所有变量和函数仍将添加到全局范围内。
但是随着网站随着Angular,React和Vue等框架的出现而发展,并且随着公司创建高级Web应用程序而不是桌面应用程序,JavaScript现在在浏览器中扮演着重要角色。结果,迫切需要使用第三方代码来执行常见任务,将代码分解为模块化文件并避免污染全局名称空间。
在ECMAScript的2015年规范引入模块JavaScript语言,它允许使用import
和export
声明。在本教程中,您将学习什么是JavaScript模块以及如何使用import
和export
组织代码。
在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将在标头中显示变量的值,x
并y
在h2
以下p
元素中显示对这些变量的操作的值。id
元素的属性是为DOM操作设置的,它将在script.js
文件中发生;该文件也将设定值x
和y
。有关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)
设置并保存这些文件。
对于带有一些小脚本的网站,这是一种划分代码的有效方法。但是,此方法存在一些问题,包括:
污染全局名称空间:对象中已经存在您在脚本中创建的所有变量sum
(difference
,等)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中的模块使用import
和export
关键字:
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
。使用import
或export
必须使用此属性的任何代码:
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)一起使用,以增加对浏览器的支持和附加功能,但它们也可以直接在浏览器中使用。
接下来,您将探索使用import
andexport
语法的更多方法。
如前所述,使用export
语法将允许您分别导入按其名称导出的值。以以下简化版本为例functions.js
:
functions.js
export function sum() {}export function difference() {}
这将允许您使用花括号导入sum
并按difference
名称命名:
script.js
import {sum, difference} from './functions.js'
也可以使用别名来重命名该函数。您可以这样做以避免在同一模块中命名冲突。在此示例中,sum
将重命名为add
,difference
并将重命名为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
现在,默认值和命名值都可用于脚本。
模块化编程设计实践使您可以将代码分成单独的组件,这有助于使代码可重复使用和保持一致,同时还可以保护全局名称空间。可以使用import
和export
关键字在本机JavaScript中实现模块接口。在本文中,您了解了JavaSvript中模块的历史记录,如何将JavaScript文件分离为多个顶级脚本,如何使用模块化方法更新这些文件,以及命名和默认导出的import
andexport
语法。
要了解有关JavaScript中的模块的更多信息,请阅读Mozilla开发人员网络上的模块。如果您想探索Node.js中的模块,请尝试我们的如何创建Node.js模块教程。
作者介绍