好的,继续你的 第 36 题:Tree Shaking 原理与常见失效原因(高频 + 难点 + 面试必考) 。
我会给你:
- 深入解释(你用得出去的那种)
- 面试官追问点
- 常见错误案例
- 速记卡片
✅ 第 36 题:什么是 Tree Shaking?它是如何工作的?为什么有时会失效?
一、什么是 Tree Shaking?(一句话版)
Tree Shaking = 在打包时通过"静态分析",删除没有被使用的代码,从而减小 bundle 体积。
关键词:
- 静态分析(static analysis)
- ESModule(import/export)
- 未被引用的代码会被"摇掉"
二、Tree Shaking 的核心原理(深入讲解)
1. 依赖于 ES Module 的"静态结构"
ESM:
javascript
import { a } from './utils.js';
浏览器和打包器在编译阶段就知道:
- 依赖哪些模块
- 从模块中引用什么
- 哪些导出没有被使用
这叫 静态依赖图(static dependency graph) 。
⚠️ CommonJS 不行,因为它是动态的:
ini
const util = require('./' + name);
这种打包器根本分析不了 → 无法 Tree Shaking。
这就是为什么 Tree Shaking 必须是 ESM。
2. 标记未使用的导出(Mark phase)
构建工具(Rollup、Webpack、Vite)会:
- 扫描所有模块
- 识别所有 export
- 找出实际被 import 的部分
- 将未使用的 export 标记为 "dead code"(死代码)
3. 死代码消除(Dead Code Elimination)
这通常在 压缩阶段(Terser / esbuild)做。
例子:
javascript
export function a() {}
export function b() {}
// 引用了 a,没引用 b
最终产物:
csharp
function a() {}
b 会直接被丢掉。
三、Tree Shaking 很容易失效(考点重点)
面试官喜欢问:Tree Shaking 什么时候会失败?
我总结出最常见的 7 大类👇
🚨 Tree Shaking 失效的 7 个经典原因
❌ 1. 使用 CommonJS(require)而不是 ESModule
ini
const util = require('./utils');
⚠️ 导出是动态的 → 无法静态分析 → 不支持 Tree Shaking。
❌ 2. 默认导出(default export)中包含多余内容
css
export default {
a: function() {},
b: function() {}
};
Tree Shaking 不能拆解对象内部成员 → 即使你只用 a,b 也不会被删。
❌ 3. 副作用(side effects)代码
arduino
// utils.js
console.log("i have side effect");
即使模块内函数未使用,该模块 依然会保留,因为有副作用。
❌ 4. 使用动态属性访问
css
export const obj = {
a: () => {},
b: () => {}
};
然后:
javascript
import { obj } from './utils.js';
obj['a']();
打包器无法确定 obj.b 是否会用到 → 无法 Tree Shake。
❌ 5. 代码中使用 "引用可能被外部调用" 的成员
典型例子:class 的方法。
javascript
export class User {
login() {}
delete() {}
}
即使你只调用 new User().login()
delete() 方法也不会被 Tree Shake。
因为类方法看上去都可能被继承或外部访问。
❌ 6. 忘记设置 sideEffects: false
package.json:
json
{
"sideEffects": false
}
告诉打包器:
"我这个包里所有文件都没有副作用,可以大胆删。"
否则,打包器会小心谨慎,不敢删多余模块。
❌ 7. 使用了使代码变动态的写法
例如:
javascript
import * as utils from './utils';
utils[aDynamicName]();
任何动态访问都会让 Tree Shaking 失效。
四、面试官追问(必备)
❓ Tree Shaking 必须依赖 ESM 吗?为什么?
必须。
因为:
- ESM 静态结构 → 编译时就知道导入/导出
- CJS 动态结构 → require 可以接变量 → 无法分析依赖
❓ 为什么 class 方法不能 Tree Shaking?
因为 class 方法属于"可动态访问":
scss
user['delete']();
打包器无法判定哪些方法是真的没用。
❓ 什么情况下一个模块一定不会被 Tree Shake?
只要模块有副作用:
arduino
import './setup.js'; // 内有副作用
即使内容没用到,也不能删。
五、速记卡片(1 分钟复习版)
🟦 Tree Shaking 速记卡片
✔ 什么是 Tree Shaking?
- 删除没用到的代码(dead code elimination)
- 基于静态分析(ESM)
✔ 为什么 CJS 不行?
- require 是动态的 → 无法静态分析
✔ Tree Shaking 失效 7 大原因
- 使用 CommonJS(require)
- default export 包含多个成员
- 模块有副作用(console、DOM 操作等)
- 动态访问属性(obj['a'])
- class 方法(可能被外部调用)
- package.json 未设置 sideEffects: false
- 使用动态 require 或动态 import 名称
如果你能把这些背熟,Tree Shaking 面试 100% 秒杀。
要继续第 37 题吗?
👉 "事件循环(Event Loop)与微任务/宏任务" 的进阶版题目?