第 31 题:Tree Shaking 原理与常见失效原因(高频 + 难点 + 面试必考)

好的,继续你的 第 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)会:

  1. 扫描所有模块
  2. 识别所有 export
  3. 找出实际被 import 的部分
  4. 将未使用的 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 大原因

  1. 使用 CommonJS(require)
  2. default export 包含多个成员
  3. 模块有副作用(console、DOM 操作等)
  4. 动态访问属性(obj['a'])
  5. class 方法(可能被外部调用)
  6. package.json 未设置 sideEffects: false
  7. 使用动态 require 或动态 import 名称

如果你能把这些背熟,Tree Shaking 面试 100% 秒杀。


要继续第 37 题吗?

👉 "事件循环(Event Loop)与微任务/宏任务" 的进阶版题目?

相关推荐
前端一课1 小时前
第 26 题:Vue2 和 Vue3 的响应式原理有什么区别?为什么 Vue3 要用 Proxy 替代 defineProperty?
前端·面试
前端一课1 小时前
第 30 题:模块化原理(CommonJS vs ESModule)
前端·面试
前端一课1 小时前
第 27 题:Promise 实现原理(含手写 Promise)
前端·面试
前端一课1 小时前
第 32 题:深入理解事件循环(Event Loop)、微任务、宏任务(详细 + 难点 + 易错点)
前端·面试
前端一课1 小时前
【前端每天一题】🔥 第 25 题:什么是 Virtual DOM?它的优缺点是什么?Diff 算法是如何工作的?
前端·面试
前端一课1 小时前
【前端每天一题】第 23 题:闭包(Closure)与作用域链(详细 + 面试模板 + 速记卡)
前端·面试
前端一课1 小时前
【前端每天一题】🔥第 22 题:HTTP vs HTTPS、TCP vs UDP 的区别
前端·面试
前端一课1 小时前
第 26 题:浏览器与 Node.js 的事件循环有什么区别?
前端·面试
前端一课1 小时前
【前端每天一题】🔥 第 24 题:Virtual DOM 中 diff 算法的核心流程(详细版
前端·面试