欢迎光临
我们一直在努力

ES6+ 模块

系列文章目录

《JavaScript 基础与进阶笔记》(前期偏基础巩固与常见面试点,后续进入闭包、异步、工程化等进阶主题)

  • 第 01 篇:数据类型与类型判断
  • 第 02 篇:变量声明与作用域
  • 第 03 篇:闭包与高阶函数
  • 第 04 篇:函数工厂
  • 第 05 篇:this 指向与绑定
  • 第 06 篇:原型与原型链
  • 第 07 篇:类与继承
  • 第 08 篇:JS 执行机制与异步队列
  • 第 09 篇:数组常用方法
  • 第 10 篇:字符串算法
  • 第 11 篇:常见手写题合集(上)
  • 第 12 篇:常见手写题合集(下)
  • 第 13 篇:Promise 与 async/await
  • 第 14 篇:数据结构基础
  • 第 15 篇:垃圾回收与内存
  • 第 16 篇:DOM 基础全面解析
  • 第 17 篇:DOM 性能与渲染
  • 第 18 篇:DOM 交互补充
  • 第 19 篇:DOM 实战案例
  • 第 20 篇:CSS 布局与可视化高频
  • 第 21 篇:移动端与 viewport
  • 第 22 篇:BOM 核心对象
  • 第 23 篇:前端路由原理
  • 第 24 篇:浏览器存储对比
  • 第 25 篇:网络与跨域
  • 第 26 篇:网络请求与实时通道
  • 第 27 篇:Service Worker、PWA 与 Web Worker
  • 第 28 篇:浏览器高级 API
  • 第 29 篇:图片懒加载
  • 第 30 篇:ES6+ 模块(本文)

文章目录

  • 系列文章目录
  • 前言
  • 一、为什么需要模块
  • 二、ESM 基本语法
    • 2.1 导出
    • 2.2 导入
    • 2.3 静态 vs 动态
  • 三、ESM vs CommonJS(面试高频)
    • 3.1 经典对比:实时绑定 vs 值拷贝
    • 3.2 互操作(了解)
  • 四、Tree Shaking 前提
  • 五、循环依赖
  • 六、浏览器与构建工具中的模块
    • 6.1 浏览器原生 ESM
    • 6.2 Vite / Webpack 角色(一句话)
  • 七、易混淆点归纳
  • 八、思考与练习
  • 总结

前言

第 29 篇讲完图片懒加载;本篇进入 第五阶段工程化。模块化是打包、Tree Shaking、路由懒加载的基础。本篇讲 ES Module(ESM) 的 import / export、与 CommonJS(CJS) 的差异、动态 import() 与 Tree Shaking 前提,以及循环依赖、Babel 转译等面试常考点。


一、为什么需要模块

问题模块化的做法
全局变量污染 每个文件有 独立作用域
依赖关系混乱 显式 import 声明依赖
难以按需加载 动态 import() 拆 chunk
打包体积大 Tree Shaking 删未使用导出

现代前端默认 ESM;Node 在 "type": "module" 或 .mjs 下也以 ESM 为主,老项目仍大量 CJS。


二、ESM 基本语法

2.1 导出

/* 命名导出 */
export const PI = 3.14;
export function add(a, b) {
return a + b;
}

/* 或先定义再导出 */
const sub = (a, b) => a b;
export { sub };

/* 默认导出(每模块最多一个) */
export default class User {
constructor(name) {
this.name = name;
}
}

2.2 导入

import User, { PI, add } from "./math.js";

import { add as plus } from "./math.js";

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

/* 仅执行模块副作用,不绑定变量 */
import "./polyfill.js";

2.3 静态 vs 动态

静态 import动态 import()
位置 必须在顶层(不能写在 if 里) 可在任意表达式位置
时机 编译阶段 确定依赖图 运行时 返回 Promise
用途 常规依赖 代码分割、按路由/条件加载

/* 动态 import — 打包器会拆成独立 chunk */
button.addEventListener("click", async () => {
const { renderChart } = await import("./chart.js");
renderChart();
});


三、ESM vs CommonJS(面试高频)

ESMCJS(Node 传统)
语法 import / export require() / module.exports
加载 编译时 静态分析 运行时 同步 require
导出绑定 实时绑定(live binding) 值拷贝(基本类型/对象引用快照)
动态加载 import() 返回 Promise require() 可写在 if 里
Tree Shaking ✅ 前提 ❌ 难静态分析

3.1 经典对比:实时绑定 vs 值拷贝

/* counter.cjs */
let n = 0;
const inc = () => ++n;
module.exports = { n, inc };

/* main.cjs */
const { n, inc } = require("./counter.cjs");
inc();
inc();
console.log(n); /* 0 — 解构时 n 是数字快照 */

/* counter.js */
export let n = 0;
export const inc = () => ++n;

/* main.js */
import { n, inc } from "./counter.js";
inc();
inc();
console.log(n); /* 2 — import 的 n 与导出模块绑定 */

3.2 互操作(了解)

  • CJS → ESM:import pkg from 'cjs-pkg'(default 常为 module.exports)。
  • ESM → CJS:import() 或 Node 的 createRequire。
  • 混用时以 构建工具 / Node 文档 为准,面试说清 静态 ESM 利于摇树 即可。

四、Tree Shaking 前提

Tree Shaking:打包时去掉 未被引用的导出,减小体积。

要能摇树,通常需要:

  • 使用 ESM(import / export),依赖图在 构建时 可分析。
  • 避免把 ESM 整包转成 CJS(Babel modules: false 或 @babel/preset-env 保留 ESM 给打包器处理)。
  • package.json 的 sideEffects:声明哪些文件有副作用(如全局 CSS、改原型),无副作用可标 false,打包器才敢删「看似未引用」的模块。
  • {
    "sideEffects": false
    }

    或:

    {
    "sideEffects": ["*.css", "./src/polyfill.js"]
    }

    /* utils.js */
    export const add = (a, b) => a + b;
    export const sub = (a, b) => a b;

    /* main.js 只用 add → 生产包中 sub 可被摇掉 */
    import { add } from "./utils.js";

    注意:import 'lodash' 整库引入往往 摇不干净;用 lodash-es + 按需路径或 babel-plugin-import 等。


    五、循环依赖

    A 引 B、B 又引 A 时,ESM 会 先创建未完成的绑定,再执行模块体。

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

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

    可能出现 undefined(在对方 export 执行前就读取)。工程上应 避免循环依赖:抽公共模块、依赖倒置、事件总线解耦。

    CJS 循环 require 返回 已执行部分的 exports 对象,同样容易踩坑。


    六、浏览器与构建工具中的模块

    6.1 浏览器原生 ESM

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

    • 默认 defer,按依赖图加载。
    • 严格模式、单例执行(同一 URL 只执行一次)。
    • 跨域脚本需 CORS(type="module")。

    6.2 Vite / Webpack 角色(一句话)

    • 开发:Vite 利用浏览器原生 ESM + 按需编译;Webpack 多走打包图。
    • 生产:通常仍 打包合并;ESM 语法经工具链输出为兼容格式,但 尽量保留静态结构 以利摇树。

    七、易混淆点归纳

  • export default 与命名导出:default 导入名可随意;命名导出必须用 {} 且名字一致(或用 as)。
  • 静态 import 会提升:依赖模块先于当前模块体执行(在依赖图拓扑序下)。
  • import() 不是宏任务面试考点,但返回 Promise,常用于懒加载。
  • CJS 的 require 不能 写在 ESM 文件顶层;Node ESM 里用 createRequire(import.meta.url)。
  • Tree Shaking ≠ 代码分割:摇树删死代码;import() 拆 chunk,二者互补(下篇 webpack 会展开)。
  • 副作用模块(改全局、注入样式)即使用不到也可能被打进包,需 sideEffects 标注。

  • 八、思考与练习

    1. 为什么 CommonJS 难以做 Tree Shaking?

    解析:require 路径可动态、加载在 运行时,打包器难以静态确定哪些导出被使用。

    2. 下面代码合法吗?为什么?

    if (flag) import { a } from "./a.js";

    解析:不合法;静态 import 必须在顶层。应改为 if (flag) await import("./a.js")。

    3. ESM 里 import { n } 后,导出模块执行 n++,导入方 console.log(n) 是多少?

    解析:若导出的是 export let n,则为 实时绑定,能读到更新后的值;CJS 解构数字则仍是快照。

    4. package.json 写 "sideEffects": false 表示什么?

    解析:除带 sideEffects 白名单的文件外,纯 JS 模块可安全删除未引用部分,利于 Tree Shaking。

    5. 路由懒加载在 Vue / React 里底层常用什么语法?

    解析:动态 import()(Vue () => import('…')、React lazy(() => import('…'))),由打包器生成独立 chunk。


    总结

    • ESM:静态 import / export,实时绑定,是 modern 前端与 Tree Shaking 的基础。
    • CJS:require 同步、值拷贝,Node 老代码常见。
    • 动态 import():运行时加载、代码分割入口。
    • 工程上避免 循环依赖,Babel 避免无脑 ESM→CJS 导致摇树失效。

    下一篇讲 Symbol 与 Iterator / Generator(系列第 31 篇,大纲 §31)。

    赞(0)
    未经允许不得转载:171主机测评 » ES6+ 模块
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址