动态加载 vs 延迟加载:为什么 demo 里「延迟」看起来没效果?

动态加载 vs 延迟加载:为什么 demo 里「延迟」看起来没效果?

我写了个小 demo 体验 ArkTS 的两种「按需加载」:点「动态加载」按钮,能明显看到模块是这会儿才被拉进来的;可点「延迟加载」按钮......好像没啥不一样?而且两种点下去,模块都只加载一次

说实话,这个「看不出来」的困惑特别好------它恰好说明:这俩长得太像了,但骨子里是两种东西。 这篇就把窗户纸捅破。先把我观察到的三个现象列出来,一个个解释:

  1. 动态加载:触发事件后才导入,而且是 await 异步导入 ✅(你看得很准)
  2. 延迟加载:好像没看出「延迟」在哪 🤔
  3. 两种都只加载一次 ✅

一、先退一步:三种导入放一起,才看得清

只盯着「动态」和「延迟」对比,容易懵,因为它俩都是「不在启动时加载」。得把静态导入也拉进来当参照物。

打个比方:把「导入一个模块」想象成「让一个员工到岗上班」。

  • 静态 import ------ 入职即全勤。 公司一开门(App 冷启动),花名册上所有员工全部到岗打卡 ,哪怕今天根本没他的活。人一多,开门就慢(冷启动变慢 )。这是我们平时 import { x } from '...' 的默认行为。

  • 延迟 import lazy ------ 挂名待命。 他照常写在花名册里(代码顶部照常 import,看着和静态一模一样 ),但开门时他不来、不占工位 (不拖慢启动);直到第一次真有活儿找到他,他才到岗。 关键点来了:你给他派活的方式,和派给普通员工没有任何区别(直接喊一嗓子,同步,不用等)------所以你「感觉不到」他是待命的。这就是你没看出来的原因。

  • 动态 import() ------ 现叫现到。 他压根不在花名册里。你需要时得专门打个电话 叫他(显式写 import()),还得等他过来 (异步,await / Promise)。什么时候叫、叫不叫,你说了算。

一句话先记住:静态是「全员到岗」,延迟是「挂名待命」,动态是「现叫现到」。


二、逐个解释你看到的现象

现象 1:动态加载「点了才导入、还要 await」------对,这是它的本色

动态 import 是个异步操作,返回 Promise:

ts 复制代码
// 不在花名册,现打电话叫人,还得等他来(await)
const ns: ESObject = await import('../lazydemo/DynamicFeature');
const msg = ns.runDynamic();

你能清楚看到「这一行执行了,模块才被拉进来」,是因为加载这个动作是你亲手写出来的import(...) 摆在那儿),而且它异步、要 await,存在感很强。

现象 2:延迟加载「没看出来」------因为我把它也绑在按钮上了

我的 demo 里,「延迟」按钮点下去会调用:

ts 复制代码
// 花名册照常写在文件顶部(看着就跟静态一样)
import lazy { runLazy } from '../lazydemo/LazyFeature';

// 用的时候,跟调用一个普通函数没有任何区别(同步,没有 await)
const msg = runLazy();   // ← 第一次执行到这里,LazyFeature 才被加载

于是它和动态一样「点了才加载」,看着就像同一回事。但差别其实藏在两处,你只要盯住就能看见:

  • 写法/调用方式 :动态要 await import()(异步、要处理 Promise);延迟就一句 runLazy()同步 ,和调用普通函数毫无区别)。你注意到「动态有 await、延迟没有」------这就是 tell
  • 谁在控制加载 :动态是你手动编排 (什么时候 import 你写死的);延迟是编译器替你悄悄推迟,对你完全透明。

换句话说,延迟加载的卖点从来不是「什么时候加载」,而是「你几乎无感」:代码照常写、同步用、不用改成 async,它自己把加载推迟到「第一次被用到」。

那它的价值怎么才看得出来?得拿它和「静态」比冷启动,而不是和「动态」比。 如果再加一个静态导入的模块,App 一启动它的日志就会出现;而延迟、动态那两个启动时都静悄悄。这一对比,延迟「给冷启动减负」的价值立刻就显形了------而且它的用法还和静态一样省心。

现象 3:「都只加载一次」------这是模块缓存,三种方式的共性

员工到岗后就一直在了,不会每次派活都重新入职一遍。模块也一样:任何模块被求值一次后就被缓存,之后再导入/再使用,拿的都是缓存。

所以「只加载一次」是静态、动态、延迟三者共有的 行为,不是谁的专属特点。你观察对了,但它不是用来区分这两者的点。(这也是为什么我 demo 里两个模块的「🟦/🟪 被加载」日志各自只冒一次,之后再点只跑函数。)


三、并排对比

还是用「员工」那套:

静态 import 延迟 import lazy 动态 import()
比喻 入职即全勤 挂名待命 现叫现到
写在哪 文件顶部 文件顶部(看着和静态一样 代码中间,用到才写
何时加载 冷启动就加载 首次用到那个名字 执行到 import() 那行
用起来 同步 同步(无感,和静态一样) 异步 ,返 Promise,要 await
路径 必须是常量 必须是常量 可以是变量
谁控制 ------ 编译器自动推迟,对你透明 你手动编排
只加载一次 ✅(三者都靠模块缓存)

四、那到底该用哪个?

按「你想要什么」来选,不纠结:

  • 「这段代码别拖慢启动,但我懒得改成异步、用法想照旧」→ 延迟 import lazy。 最省心,几乎零改动,把顶部的 import 加个 lazy 就行。绝大多数「单纯想给冷启动减负」的场景用它最合适。
  • 「运行期才决定加不加载、加载哪一个(路径是变量)、或者要等网络/条件满足」→ 动态 import()。 最灵活,代价是你得处理 Promise、把调用链改成异步。
  • 一个经验法则:先想清楚是「想省启动时间」还是「想动态控制」。 前者优先 lazy(改动小),后者才上 import()

小提醒:import lazy 只支持具名 / 默认导入,import lazy * as ns 会编译报错;本工程是 API 23,直接用即可(API 12 上还得在 build-profile.json5compatibleSdkVersionStage: "beta3")。只有变量形式 的动态 import(变量) 才需要配 arkOptions.runtimeOnly


一句话总结

动态加载是「现叫现到」------你亲手打电话(import())、还得等(await),存在感强;延迟加载是「挂名待命」------写法和静态一模一样、用起来也同步无感,它只是悄悄把加载推迟到第一次用到。 你「没看出延迟」,恰恰是因为它做到了「让你无感」;要看出它的价值,拿它和「静态导入启动就加载」比,而不是和「动态」比。至于「只加载一次」,那是模块缓存,三种方式都一样。


附:想让「延迟」的效果一眼可见?

给 demo 再加一个静态导入的模块做对照即可:

ts 复制代码
// 静态:App 一启动,StaticFeature 顶层日志就会出现在屏幕上
import { runStatic } from '../lazydemo/StaticFeature';

启动时你会看到:静态那条日志已经在了,而延迟、动态两条都还没出现。这时候「延迟 = 写法像静态,但不在启动时加载」就一目了然了。

相关推荐
cypking1 小时前
从零搭建 Claude Code + Chrome MCP 浏览器自动化:前端 E2E 端到端测试完整教程(包含增量测试)
前端·chrome·自动化
Levi_J1 小时前
Vue2 升级 Vue3 项目实战
前端
前端拷贝猿1 小时前
扫码领券功能需求分析
前端
前端拷贝猿1 小时前
设备活动弹窗功能需求分析
前端
飞天狗1111 小时前
零基础JavaWeb入门——第五课第一小节:九大内置对象 · 第1个:request(请求对象)
java·开发语言·前端·后端·servlet
a15108416932 小时前
记一次大模型探索
java·服务器·前端
石山代码2 小时前
变量与解构
开发语言·前端·javascript
888CC++2 小时前
箭头函数(ES6)
前端·javascript·es6
qq_419854052 小时前
css filter
前端·javascript·css