懒执行与懒加载
面试速答(30 秒版 TL;DR)
- 懒执行(Lazy Execution) 关注的是:代码什么时候算、什么时候跑。
- 懒加载(Lazy Loading) 关注的是:资源什么时候下载、什么时候进入页面。
- 两者经常一起出现,但不是一回事:
- 懒加载解决“先别下载”
- 懒执行解决“先别计算、先别初始化”
- 典型例子:
- 路由组件
import():既可能触发懒加载,也可能把模块执行推迟到真正进入路由时 - 图片
loading="lazy":是懒加载,不是懒执行 - Vue
computed:典型懒执行,不涉及资源加载
- 路由组件
一、为什么这一题很容易混?
因为很多优化动作表面上都叫“lazy”,但它们作用在不同层面:
- 有的是 不下载
- 有的是 下载了但先不执行
- 有的是 执行结果先不计算
- 有的是 等用户看到、点击、滚动到再触发
如果面试时不先分层,很容易把很多不同机制混成一句“按需加载”。
更稳的说法是:
懒加载管“资源进入系统的时机”,懒执行管“逻辑真正运行的时机”。
二、先区分:资源层 vs 执行层
| 维度 | 懒加载 | 懒执行 |
|---|---|---|
| 核心问题 | 资源何时下载 | 逻辑何时运行 |
| 优化对象 | 图片、脚本、模块、组件、iframe | 计算、初始化、副作用、监听器、渲染逻辑 |
| 常见手段 | loading="lazy"、动态 import()、路由分包 | 惰性求值、按事件触发、按可见性触发、延迟初始化 |
| 目标 | 降低首屏请求与传输压力 | 降低首屏主线程压力与无效计算 |
这张表最好能记住,因为它能把回答立刻拉开层次。
三、懒加载到底在优化什么?
懒加载的本质是:
当前不用的资源,不要在首屏阶段就把它们都拉下来。
典型对象包括:
- 首屏以下图片
- 路由级页面代码
- 很重的图表库、编辑器、地图 SDK
- 评论区、推荐区、客服脚本
- 视口外 iframe
示例 1:图片懒加载
<img src="/images/card.webp" loading="lazy" alt="商品图" width="320" height="240" />
示例 2:路由组件懒加载
const UserPage = () => import('./pages/UserPage.js')
懒加载的直接收益通常是:
- 首屏请求数更少
- 首屏下载体积更小
- 当前页面关键资源竞争更少
四、懒执行又在优化什么?
懒执行的本质是:
当前不需要的逻辑,先别跑;等真正有人用到时再运行。
典型场景:
- 大对象或大列表的派生计算
- 弹窗、抽屉、富文本编辑器的初始化
- 图表首次渲染
- 非关键埋点、监控、推荐算法
- 只有用户交互后才需要的副作用逻辑
一个最小例子:
let panelInstance
function openPanel() {
if (!panelInstance) {
panelInstance = createHeavyPanel()
}
panelInstance.show()
}
这里没有网络请求优化,但明显减少了初始执行成本。
五、两者经常如何配合?
很多真实优化并不是二选一,而是一起用。
比如一个图表面板:
- 首屏不下载图表库代码
- 用户点开“数据分析”标签页时,再动态 import 图表模块
- 模块下载后,真正渲染图表也等容器可见时再初始化
这就是:
- 第一层:懒加载
- 第二层:懒执行
先记两层决策:要不要先下载,下载后要不要立刻执行。
这张图要表达的重点是:
先决定下不下载,再决定下完后是不是立刻执行。
六、典型手段有哪些?
1. 图片、iframe 原生懒加载
<img src="/images/list-item.webp" loading="lazy" alt="列表图片" />
<iframe src="https://example.com/embed" loading="lazy" title="第三方内容"></iframe>
2. 动态 import
button.addEventListener('click', async () => {
const { mountEditor } = await import('./editor.js')
mountEditor()
})
这段代码里同时包含两层含义:
editor.js是按需下载mountEditor()是按点击时机执行
3. 视口可见时再启动逻辑
const observer = new IntersectionObserver(async ([entry]) => {
if (!entry.isIntersecting) return
const { mountChart } = await import('./chart.js')
mountChart(entry.target)
observer.disconnect()
})
observer.observe(document.querySelector('#chart'))
这是很典型的“懒加载 + 懒执行”组合拳。
七、它们分别会踩什么坑?
懒加载常见坑
- 首屏关键资源也被懒加载,反而拖慢 LCP
- 图片懒加载但没有预留尺寸,导致 CLS
- 包拆太碎,请求调度变复杂
- 路由切换时才发现 chunk 很大,切页卡顿
懒执行常见坑
- 首次交互时集中初始化,导致点击后卡顿
- 逻辑延后后,状态依赖没理顺,产生时序问题
- 过度推迟埋点或监听,导致丢数据
- 某些逻辑每次触发都重新初始化,反而重复开销更大
面试里如果你能补这一层“优化也会转移成本”,答案会更成熟。
八、怎么判断该不该用?
可以用一个很实用的判断顺序:
- 这个资源或逻辑是不是首屏必需?
- 如果现在不下载/不执行,用户会不会立刻感知异常?
- 延后之后,是否会把成本堆到首次交互时爆发?
- 是否有明确触发条件,比如点击、滚动、进入视口、切换路由?
如果一个功能是首屏主内容、关键交互、LCP 元素,那通常不适合盲目懒处理。
九、典型题与标准答法
Q1:懒执行和懒加载有什么区别?
答法:
懒加载解决的是资源什么时候下载,懒执行解决的是逻辑什么时候运行。一个偏资源调度,一个偏运行时计算。它们经常一起使用,但不是同一个概念。
Q2:动态 import 算懒加载还是懒执行?
答法:
首先它一定包含懒加载,因为模块不是首屏就下载;其次它通常也会把模块执行推迟到真正触发 import 的时机。所以很多场景里它同时体现了两者。
Q3:是不是所有非首屏内容都该懒处理?
答法:
不是。要看它是否会拖慢关键路径,以及延后后会不会把压力堆到用户首次交互。优化不能只看“首屏更轻了”,还要看“后续会不会更卡”。
Q4:懒加载一定能提升性能吗?
答法:
不一定。它通常能减轻首屏负担,但也可能带来切页等待、首次点击卡顿、请求碎片化等副作用。关键是把资源和逻辑延后到合适的时机,而不是机械地全部按需。
十、常见误区
-
误区 1:把所有 lazy 都叫按需加载。 最好分清资源和执行两个层面。
-
误区 2:首屏关键图也开
loading="lazy"。 这会伤害 LCP。 -
误区 3:只考虑首屏,不考虑首次交互。 成本只是被转移了,不一定被消灭了。
-
误区 4:拆包越细越好。 包太碎会带来新的调度成本。
十一、速记要点
- 懒加载:先别下载。
- 懒执行:先别运行。
- 前者优化请求和传输,后者优化主线程和计算。
- 动态 import 常常同时具备两者特征。
- 优化不是无脑延后,而是把工作放到真正需要的时机。