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