动态加载 vs 延迟加载:为什么 demo 里「延迟」看起来没效果?
2026/6/16 14:16:52 网站建设 项目流程

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

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

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

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

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

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

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

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

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

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

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


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

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

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

// 不在花名册,现打电话叫人,还得等他来(await)constns:ESObject=awaitimport('../lazydemo/DynamicFeature');constmsg=ns.runDynamic();

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

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

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

// 花名册照常写在文件顶部(看着就跟静态一样)importlazy{runLazy}from'../lazydemo/LazyFeature';// 用的时候,跟调用一个普通函数没有任何区别(同步,没有 await)constmsg=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 再加一个静态导入的模块做对照即可:

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询