JavaScript/模块
在 JavaScript 的早期,脚本相对较小。在许多情况下,完整的函数都驻留在单个 JavaScript 文件中。随着时间的推移,需求和解决方案显著增长。主要的是,经常使用的功能被转移到单独的文件中。随着这种复杂性的增长,意外副作用的危险也随之增长,源代码的模块化需求变得明显。
原始的 JavaScript 语法 - 今天仍然有效 - 不知道不同脚本文件中编写的源代码之间的边界。无论文件组织如何,所有内容在任何地方都是已知的。以下示例显示了两个函数在 HTML 内部和彼此之间都是已知的。一个可以调用另一个。
<!DOCTYPE html>
<html>
<head>
<script>
alert("In script 1");
function function_1 () {
"use strict";
alert("In function_1");
function_2();
}
</script>
<script>
alert("In script 2");
function function_2 () {
"use strict";
alert("In function_2");
}
</script>
</head>
<body>
<button onclick="function_1()">Click</button>
</body>
</html>
当 HTML 页面加载时,浏览器会读取这两个script
部分;因此显示了两个alert
消息。单击按钮后,会调用function_1
,它会调用function_2
。
将脚本转移到外部文件并通过<script src="./function_1.js"></script>
引用它们时,也会出现相同的行为。
为了避免从一个函数到另一个函数或从一个文件到另一个文件出现意外副作用的可能性,已经开发了多种形式的模块化。
自 ECMAScript 2015 (ES6) 以来,标准定义了模块及其行为的语法。大多数浏览器都原生支持它,无需任何额外的库。
关于 HTML,语法略有变化。<script>
元素必须通过类型属性<script type="module">
扩展。这声明一个文件或内联脚本是一个模块。之后,它的内部类、函数、变量……不再对其他内联脚本、文件或 HTML 可见。在以下示例中,单击按钮会导致错误消息,因为带有其函数function_1的内联脚本被视为一个模块。
<!DOCTYPE html>
<html>
<head>
<script type="module">
alert("In script 1");
function function_1 () {
"use strict";
alert("In function_1");
}
</script>
</head>
<body>
<button onclick="function_1()">Click</button>
</body>
</html>
由于安全原因,使用纯内联脚本创建示例很复杂。我们使用带有外部文件的示例。在测试时,请按以下内联脚本所示创建此文件。
- 当 HTML 页面加载时,它会显示一条警报消息(第 7 行)。
- 外部文件function_1.js将它的函数function_1发布到公共(第 16 行)。所有其他功能都保持隐藏(在这个简单的示例中,没有其他功能)。
- 函数function_1被导入到内联脚本中(第 5 行)。
- 我们通过
addEventListener
将事件侦听器添加到按钮(第 10 行)。事件侦听器包含一个匿名函数,该函数调用function_1
(在外部文件中)。在第 10 行,事件侦听器仅声明;此时,它不会被调用。 - "use script" 语句变得多余,因为模块始终以严格模式运行。
<!DOCTYPE html>
<html>
<head>
<script type="module">
import {function_1} from "./function_1.js";
alert("In script 1");
const btn1 = document.getElementById("btn1");
btn1.addEventListener("click", () => function_1());
/* create a file 'function_1.js' with the following content:
function function_1() {
alert("In function_1");
}
export { function_1 };
*/
</script>
</head>
<body>
<button id="btn1">Click</button>
</body>
</html>
本质上,ES6 模块语法由两个语句export
和import
组成。export
用于发布模块,使其部分类、函数或变量公开可用。import
用于调用脚本,以访问这些对象。
Web 服务器不受 HTML 元素的控制。因此,它们过去使用不同的技术来决定哪些 JS 脚本应该被视为模块,哪些不应该。
流行的 Web 服务器node.js支持ES 模块的export/import语法。但它的默认模块系统不同。它被称为CommonJS。
要告诉node.js使用哪种语法,必须在项目的package.json
文件中添加一行。
{
..
"type": "module",
..
导致ES 模块语法。另一种方法是使用 '.mjs' 扩展名代替文件名的 '.js'。"type": "commonjs"
行(或无定义)会导致 node.js 特定的语法CommonJS。
在CommonJS中,导出使用modules.exports
(请注意额外的 's')完成,导入使用require
语句完成。一个例子
// export in a file 'logger.js'
...
function doLogging() { ... };
module.exports = {doLogging};
// import in a file 'main.js'
const doLogging = require('./logger.js')
...