这是我参与8月更文挑战的第12天,活动详情查看: 8月更文挑战
前言
随着应用越来越大,越来越复杂,我们想要将其拆分成多个文件,即所谓的“模块(module)”。一个模块可以包含用于特定目的的类或函数库。 但是最终脚本变得越来越复杂,因此社区发明了许多种方法来将代码组织到模块中,使用特殊的库按需加载模块。
传统的模块系统
- AMD —— 最古老的模块系统之一,最初由 require.js 库实现。
- CommonJS —— 为 Node.js 服务器创建的模块系统。
- UMD —— 另外一个模块系统,建议作为通用的模块系统,它与 AMD 和 CommonJS 都兼容。
小结:语言级的模块系统在 2015 年的时候出现在了标准(ES6)中,此后逐渐发展,现在已经得到了所有主流浏览器和 Node.js 的支持。因此,我们将从现在开始学习现代 JavaScript 模块(module)。
模块的本质
一个模块就是一个文件,一个脚本就是一个模块,模块之间可以互相加载,可以使用export和import来交换功能,互相调用其中的方法。
- export 关键字标记了可以从当前模块外部访问的变量和函数。
- import 关键字允许从其他模块导入功能。
例子:
// sayHi.js
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
……然后另一个文件可能导入并使用了这个函数:
// main.js
import {sayHi} from './sayHi.js';
alert(sayHi); // function...
sayHi('coolFish'); // Hello, coolFish!
import 指令通过相对于当前文件的路径 ./sayHi.js 加载模块,并将导入的函数 sayHi 分配(assign)给相应的变量。 让我们在浏览器中运行一下这个示例。 由于模块支持特殊的关键字和功能,因此我们必须通过使用
<!doctype html>
<script type="module">
import {sayHi} from './say.js';
document.body.innerHTML = sayHi('coolFish');
</script>
模块的导入
如果同一个模块被导入到多个其他位置,那么它的代码仅会在第一次导入时执行,然后将导出(export)的内容提供给所有的导入(importer)。 也就是说,模块只会编译一次,然后将 export 的内容,互相分享使用。
例子:
// 1.js
import {admin} from './admin.js';
admin.name = "Pete";
// 2.js
import {admin} from './admin.js';
alert(admin.name); // Pete
小结: 1.js 和 2.js 导入的是同一个对象 在 1.js 中对对象做的更改,在 2.js 中也是可见的。所以模块只被执行一次。生成导出,然后它被分享给所有对其的导入,所以如果某个地方修改了 admin 对象,其他的模块也能看到这个修改。 这种行为让我们可以在首次导入时 设置 模块。我们只需要设置其属性一次,然后在进一步的导入中就都可以直接使用了。
模块中的this
这是一个小功能,但为了完整性,我们应该提到它。 在一个模块中,顶级 this 是 undefined。 将其与非模块脚本进行比较会发现,非模块脚本的顶级 this 是全局对象
<script>
alert(this); // window
</script>
<script type="module">
alert(this); // undefined
</script>