发布于 2026-01-06 0 阅读
0

JavaScript:ES6 模块变得简单 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

JavaScript:ES6 模块入门

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

在 ES6 之前,JavaScript 中没有原生的模块导入系统。

虽然有像 CommonJS 这样的工具,但 JavaScript 语言规范中并没有内置相关功能。其他主流语言似乎都有实现这种功能的方法,因此 JavaScript 缺乏这种功能确实印证了那些认为 JavaScript 只是“玩具语言”的人的观点。

在本文中,我将探讨为什么我们需要 JavaScript 中的模块,以及如何创建和使用它们。

为什么我们需要模块?

如果没有模块,默认情况下,我们应用程序中包含的所有代码,无论是来自第三方代码还是我们自己的代码,其作用域都将是全局的。

现代 JavaScript 应用程序可能会使用成千上万个导入的函数(不仅是你使用的库,还有它们使用的库等等)。如果所有函数都是全局的,那将会形成一个极其混乱的全局命名空间。每次创建新函数时,你都会担心出现命名冲突。最好的情况是,当你定义一个已被占用的函数时,会立即报错。最糟糕的情况是,函数名会被悄悄覆盖,导致一个极其难以发现的 bug。

揭示模块模式

过去,这个问题通常是通过临时解决方案解决的,一般采用揭示模块模式。这种模式的一个例子如下:

const public = (function () {
  var hidden = true;
  function private1 () {}
  function private2 () {}
  return {
    private1,
    private2,
  };
})();

结果是,`private1`、`private2` 和 `hidden` 仅在其所在的函数作用域内有效,它们不存在于全局作用域中。全局作用域中存在的都是公共属性。`public` 是一个变量,它引用一个具有名为 `private1` 和 `private2` 的属性的对象。这些属性是我们从模块中导出的函数。

虽然这个方法奏效了,但它也存在一些问题:

  • 必须编写自执行闭包,这很烦人,而且是丑陋的样板代码。
  • 由于它并非语言标准中内置的“官方”功能,第三方代码可能根本无法实现。
  • 由于缺乏标准,不同的库可能会以不同的方式实现此功能,从而导致混乱。

为了解决这些问题,ES6 引入了模块。

默认导出

ES6 模块只是一个 JavaScript 文件,它导出某些表达式,然后可以在代码的其他地方导入这些表达式。

导出项可以是默认的,也可以是命名的。我们先来看默认导出项。

const secretNumber = 123;
export default class User;

默认导出是通过使用 export 关键字,后跟 default 关键字,再后跟要导出的表达式来完成的,在本例中是 User 类定义。

默认导出文件按如下方式导入:

import User from './user';
const user = new User('wellpaidgeek@gmail.com');

这里,用户将在其中一个js文件中定义并导出,然后在另一个js文件中导入和使用。每个js文件都是一个独立的模块。

在导入语句中使用用户路径('./user')时,应该是从当前要导入的文件到该文件的相对路径。

请注意,使用默认导出时,我们为导入的内容命名完全是任意的。它不必与导出时的名称一致。这意味着上面的代码可以写成如下形式,并且仍然能够正常工作:

import ICanCallThisAnythingAndItIsStillAUserClass from './user';
const user = new ICanCallThisAnythingAndItIsStillAUserClass('wellpaidgeek@gmail.com');

模块不必有默认导出项,但如果有,则只能有一个。因此,以下代码无效:

const func1 = () => {};
const func2 = () => {};

export default func1;
export default func2;

我们可以出口哪些类型的产品?

任何表达式。包括变量、函数、类和字面量。以下所有内容都是有效的默认导出:

export default 99;
export default 'foo';
export default 10 + 10;
export default () => { console.log('EXPORTED'); };
const x = 10;
export default x;

命名出口

我们还可以使用另一种类型的导出,称为命名导出。例如:

// maths.js
export const pi = 3.142;
export const factorial = x => {
    if (x < 2) {
        return 1;
    }
    return x * factorial(x - 1);
};

// main.js
import { pi, factorial } from './maths';

const myNumber = factorial(4) + pi;

'maths.js' 导出了两个命名函数:pi 和 factorial。'main.js' 正在使用它们。

与默认导出(每个模块只能有一个默认导出)不同,一个模块可以拥有任意数量的命名导出。另一个区别是,命名导出必须指定一个名称,并且必须使用该名称导入。导入命名导出时,所有要导入的导出名称必须以逗号分隔,并用花括号括起来。

如何给导出项命名?导出项的名称是我们用于表达式的标识符。它可以是函数名、变量/常量名或类名。在 maths.js 中,使用的是常量名。

其他命名示例:

export class User {} // name: User
export function generatePassword () {} // name: generatePassword
export const apiKey = '123'; // name: apiKey

混合使用默认导出和命名导出

如果我们希望一个模块既有默认导出,又有命名导出,该怎么办?这很简单,可以这样实现:

// user.js
export default class User {}

export function generatePassword () {}
export const generateUniqueUserId = () => {};

// main.js
import User, { generatePassword, generateUniqueUserid } from './user';

默认导入语句必须放在最前面,然后是一个逗号,接着是我们要导出的命名语句列表,用花括号括起来。

为命名导入添加别名

你可能已经注意到命名导入的一个缺陷。如果我们导入的某个模块与另一个模块存在命名冲突怎么办?别担心,ES6 的设计者们已经考虑到了这一点。他们赋予了我们为命名导出模块创建别名的功能。

如果我们有两个模块,module1 和 module2,它们各自都有一个名为“calculate”的导出项,那么我们可以这样为它们设置别名,以避免导入它们的模块中出现命名冲突:

import { calculate as module1Calculate } from './module1';
import { calculate as module2Calculate } from './module2';

module1Calculate();
module2Calculate();

使用模块

在 Chrome 等现代浏览器中,您可以通过在 HTML 页面中包含模块时,在 script 标签中指定 type="module" 来使用模块。如果您有一个名为 user 的模块,以及一个名为 main 的模块(该模块从 user 导入),则可以在网页中这样包含它们:

<script type=”module” src=”user.js”></script>
<script type=”module” src=”main.js”></script>

虽然我知道这样做是可行的,但我从不这么做,主要是因为并非所有浏览器都完全支持这种方式。我通常使用 webpack 和 babel 的组合,将所有模块编译成一个单独的 bundle 进行部署。这超出了本文的讨论范围(文章已经够长了!)。一个简单的尝试方法是使用create-react-app创建一个简单的 React 应用。然后,你可以在 src 文件夹中创建模块,并练习如何从这些模块导入到 App.js 中。

喜欢这篇文章吗?那你一定会喜欢我的邮件列表。我会定期发送关于 JavaScript、技术和职业发展的简讯。已有超过 5000 人喜欢阅读,快来加入我们吧!点击此处订阅

文章来源:https://dev.to/wellpaidgeek/javascript-es6-modules-made-simple-2990