网站

JavaScript 模块 | MDN

Module 的语法 | 阮一峰

引入

项目变大之后,所有代码写在一个文件里会有几个问题:文件太长不好找代码、变量名容易冲突、不知道哪个文件依赖哪个。

模块化就是把代码按功能拆分成多个文件,每个文件只负责一部分,需要的时候再引入。

正文

定义

模块(Module)是一个独立的 JavaScript 文件,通过 export 导出功能,其他文件通过 import 引入使用。

模块的基本规则

每个模块文件有自己的作用域,内部的变量和函数不会污染全局。

只有显式 export 的内容才能被外部访问,其他都是私有的。

在 HTML 中使用模块,<script> 标签必须加 type="module"

<script type="module" src="main.js"></script>

export 导出

命名导出(Named Export)

一个文件可以导出多个内容,导入时需要用 {} 指定名字。

export function add(a, b) {
  return a + b;
}
 
export function subtract(a, b) {
  return a - b;
}
 
export const PI = 3.14159;

也可以先定义,最后统一导出:

function add(a, b) {
  return a + b;
}
 
function subtract(a, b) {
  return a - b;
}
 
export { add, subtract };

默认导出(Default Export)

一个文件只能有一个默认导出,导入时名字随意。

export default class Calculator {
  add(a, b) {
    return a + b;
  }
 
  subtract(a, b) {
    return a - b;
  }
}
export default function greet(name) {
  console.log(`你好,${name}`);
}

import 导入

命名导入

{} 导入对应的命名导出。

import { add, subtract, PI } from "./math.js";
 
console.log(add(3, 5));
console.log(PI);

导入时可以重命名:

import { add as sum } from "./math.js";
 
console.log(sum(1, 2));

默认导入

不需要 {},名字可以随意取。

import Calculator from "./Calculator.js";
 
const calc = new Calculator();
 
console.log(calc.add(1, 2));

同时导入默认和命名

import Calculator, { add, subtract } from "./Calculator.js";

导入所有内容

* 把模块所有导出收集到一个对象里。

import * as math from "./math.js";
 
console.log(math.add(1, 2));
console.log(math.PI);

模块的作用域

模块内部的变量对外不可见,除非 export

const secret = "这是私有的";
 
export function getSecret() {
  return secret;
}

外部只能通过 getSecret() 访问 secret,不能直接使用。

不同模块之间的变量互不影响:

let count = 0;
 
export function increment() {
  count = count + 1;
  return count;
}

这个 count 只属于这个模块,不会和其他模块的 count 冲突。

动态导入

import() 返回一个 Promise,可以在运行时按需加载模块。

async function loadModule() {
  const module = await import("./math.js");
 
  console.log(module.add(1, 2));
}
 
loadModule();

适合用在需要时才加载的代码,比如点击按钮后加载某个功能模块。

button.addEventListener("click", async () => {
  const { initChart } = await import("./chart.js");
  initChart();
});

实战例子

拆分数学工具

math.js

export function add(a, b) {
  return a + b;
}
 
export function multiply(a, b) {
  return a * b;
}
 
export const PI = 3.14159;

main.js

import { add, multiply, PI } from "./math.js";
 
const result = add(10, multiply(3, PI));
 
console.log(result);

拆分用户模块

user.js

const users = [];
 
export function addUser(name, email) {
  users.push({ name, email });
}
 
export function getUsers() {
  return [...users];
}
 
export function findUser(name) {
  return users.find(u => u.name === name);
}

app.js

import { addUser, getUsers, findUser } from "./user.js";
 
addUser("Tom", "[email protected]");
addUser("Jack", "[email protected]");
 
console.log(getUsers());
 
const user = findUser("Tom");
 
console.log(user);

index.html

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>模块化示例</title>
</head>
<body>
  <script type="module" src="app.js"></script>
</body>
</html>

常见写法

写法说明
export function fn()命名导出函数
export const val命名导出变量
export default默认导出
import { fn } from "./file.js"命名导入
import Name from "./file.js"默认导入
import * as all from "./file.js"导入所有
import("./file.js")动态导入

理解

模块化可以理解为给代码分房间

每个文件是一个房间,房间里的东西默认外面看不到。只有你主动把某些东西放到门口(export),别人才能拿到。

别人要用的时候,需要明确说”我要哪个房间里的哪个东西”(import)。

这样每个房间保持独立,互不打扰,找东西的时候也知道去哪间房找。

拆分的核心原则:数据规则放一起,界面渲染放一起,交互状态放一起

引出

模块化是 JS ES6 的重要特性之一,配合 JS 函数JS 对象 可以更好地组织代码。

实际项目中经常用模块化配合 JS 异步编程 按需加载代码,提升页面性能。