ES Modules

ES Modules概述

ES Modules是ES6标准的模块规范。

ES Modules特点

// ES Modules特点:
// 1. 编译时静态分析
// 2. 异步加载
// 3. 输出值的引用
// 4. this指向undefined
// 5. 自动采用严格模式

// 使用方式
// 浏览器:<script type="module">
// Node.js:.mjs扩展名或package.json中设置type: "module"

基本用法

<!-- 浏览器中使用 -->
<script type="module">
    import { name } from "./module.js";
    console.log(name);
</script>

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

export导出

导出模块内容。

命名导出

// 导出变量
export const name = "东巴文";
export const age = 25;

// 导出函数
export function greet() {
    return "Hello";
}

// 导出类
export class User {
    constructor(name) {
        this.name = name;
    }
}

// 先定义后导出
const url = "db-w.cn";
function fetch() {}
export { url, fetch };

// 重命名导出
export { url as siteUrl };
export { fetch as fetchData };

默认导出

// 默认导出
export default "东巴文";

// 导出函数
export default function(name) {
    return `Hello, ${name}`;
}

// 导出类
export default class User {
    constructor(name) {
        this.name = name;
    }
}

// 导出对象
export default {
    name: "东巴文",
    url: "db-w.cn"
};

// 先定义后导出
const config = { debug: true };
export default config;

混合导出

// 命名导出 + 默认导出
export const name = "东巴文";
export const age = 25;
export default class User {
    constructor(name) {
        this.name = name;
    }
}

// 重新导出
export { name as userName } from "./user.js";
export * from "./utils.js";
export { default } from "./config.js";

import导入

导入模块内容。

命名导入

// 导入命名导出
import { name, age } from "./module.js";
console.log(name, age);

// 重命名导入
import { name as userName } from "./module.js";
console.log(userName);

// 导入所有命名导出
import * as module from "./module.js";
console.log(module.name);
console.log(module.age);

默认导入

// 导入默认导出
import User from "./module.js";
const user = new User("东巴文");

// 导入默认和命名
import User, { name, age } from "./module.js";

// 重命名默认导入
import { default as MyUser } from "./module.js";

动态导入

// 动态导入返回Promise
async function loadModule() {
    const module = await import("./module.js");
    console.log(module.name);
}

// 条件导入
if (condition) {
    import("./module.js").then(module => {
        module.init();
    });
}

// 按需加载
button.addEventListener("click", async () => {
    const { showDialog } = await import("./dialog.js");
    showDialog();
});

仅执行模块

// 只执行模块代码,不导入任何内容
import "./polyfill.js";
import "./styles.css";

// 副作用模块
// analytics.js
console.log("分析脚本加载");
window.track = function() {};

模块特性

ES Modules的特殊行为。

静态结构

// import/export必须在顶层
// 编译时确定依赖关系

// 正确
import { name } from "./module.js";
export const x = 1;

// 错误
if (condition) {
    import { name } from "./module.js";  // SyntaxError
}

// 动态导入例外
if (condition) {
    import("./module.js").then(module => {});  // 正确
}

值引用

// counter.js
export let count = 0;
export function increment() {
    count++;
}

// main.js
import { count, increment } from "./counter.js";

console.log(count);  // 0
increment();
console.log(count);  // 1(值引用,实时更新)

// 不能直接修改
// count = 10;  // SyntaxError

只读特性

// module.js
export const name = "东巴文";
export let count = 0;

// main.js
import { name, count } from "./module.js";

// 不能重新赋值
// name = "new";   // TypeError(const)
// count = 10;     // TypeError(只读)

// 但可以修改对象属性
export const user = { name: "东巴文" };

import { user } from "./module.js";
user.name = "db-w.cn";  // 允许

严格模式

// ES Modules自动使用严格模式
// 不需要"use strict"

// 模块中的this
console.log(this);  // undefined

// 不允许未声明变量
// x = 1;  // ReferenceError

// 不允许重复参数
// function foo(a, a) {}  // SyntaxError

模块加载

ES Modules的加载机制。

浏览器加载

<!-- 模块脚本 -->
<script type="module" src="app.js"></script>

<!-- 内联模块 -->
<script type="module">
    import { name } from "./module.js";
</script>

<!-- 特点 -->
<!-- 1. 自动延迟加载 -->
<!-- 2. 支持async -->
<!-- 3. 跨域需要CORS -->
<!-- 4. 需要服务器或本地服务器 -->

<!-- nomodule回退 -->
<script type="module" src="app.js"></script>
<script nomodule src="app-legacy.js"></script>

预加载

<!-- 预加载模块 -->
<link rel="modulepreload" href="./module.js">

<!-- 预加载依赖 -->
<link rel="modulepreload" href="./utils.js">
<link rel="modulepreload" href="./api.js">
<script type="module" src="app.js"></script>

Node.js加载

// 方式1:使用.mjs扩展名
// module.mjs
export const name = "东巴文";

// 方式2:package.json设置type
// package.json
{
    "type": "module"
}

// 然后可以使用.js
// module.js
export const name = "东巴文";

// Node.js内置模块
import fs from "fs";
import path from "path";

// node:协议
import fs from "node:fs";

模块循环依赖

处理循环依赖问题。

循环依赖示例

// a.js
import { b } from "./b.js";
export const a = "a";
console.log("a.js:", b);

// b.js
import { a } from "./a.js";
export const b = "b";
console.log("b.js:", a);

// main.js
import "./a.js";
// 输出:
// b.js: undefined(a还未导出)
// a.js: b

解决方案

// 方案1:重构代码,避免循环

// 方案2:延迟访问
// a.js
import { b } from "./b.js";
export const a = "a";
export function getB() {
    return b;
}

// b.js
import { getB } from "./a.js";
export const b = "b";
export function getA() {
    return getB();
}

// 方案3:动态导入
// a.js
export const a = "a";
export async function getB() {
    const { b } = await import("./b.js");
    return b;
}

实际应用

ES Modules的实际使用。

项目结构

src/
├── index.js
├── modules/
│   ├── user.js
│   ├── post.js
│   └── comment.js
├── utils/
│   ├── format.js
│   └── request.js
└── config/
    └── index.js

模块示例

// utils/format.js
export function formatDate(date) {
    return date.toLocaleDateString();
}

export function formatCurrency(amount) {
    return new Intl.NumberFormat("zh-CN", {
        style: "currency",
        currency: "CNY"
    }).format(amount);
}

// modules/user.js
import { request } from "../utils/request.js";

export class User {
    constructor(data) {
        Object.assign(this, data);
    }
    
    static async findById(id) {
        const data = await request(`/api/users/${id}`);
        return new User(data);
    }
    
    async getPosts() {
        const { getPosts } = await import("./post.js");
        return getPosts(this.id);
    }
}

// index.js
import { User } from "./modules/user.js";
import { formatDate } from "./utils/format.js";

async function init() {
    const user = await User.findById(1);
    console.log(formatDate(user.createdAt));
}

init();

下一步

掌握了ES Modules后,让我们继续学习:

  1. 模块打包工具 - 学习打包工具
  2. 函数式编程基础 - 学习函数式编程
  3. 函数式编程技术 - 学习高级函数式编程

东巴文(db-w.cn) - 让编程学习更简单

🎯 东巴文寄语:ES Modules是JavaScript官方的模块标准,静态分析、值引用和异步加载是其核心特性。在现代前端开发中,ES Modules已成为主流选择。在 db-w.cn,我们帮你掌握现代模块化开发!