前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库动态 import() 返回 Promise 原因
JavaScript模块与语言机制

动态 import() 返回的是什么?为什么它是异步的?

import() 返回一个 Promise,resolve 的值是模块的命名空间对象,里面包含所有导出。它必须是异步的,因为模块文件可能还没下载过来,浏览器要发请求、解析源码、执行模块代码,这些步骤不可能同步完成。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#模块与语言机制#异步编程
先看核心答案读代码示例
理解线索

`import()` 的三个关键点

  1. 返回值Promise,resolve 后拿到包含所有导出的命名空间对象
  2. 异步原因模块可能要从网络加载、解析语法、链接依赖、执行代码
  3. 与静态 import 的区别静态 import 在编译期处理,import() 在运行时才触发

import() 看着像函数调用,但它是运算符。不能赋值给变量,也不能用 .call() 或 .apply()。

核心回答

先记住这个答案

import() 是一个运行时加载模块的运算符,返回一个 Promise。这个 Promise resolve 之后,拿到的是模块的命名空间对象,具名导出是它的属性,默认导出挂在 .default 上。它必须是异步的,因为模块文件可能还没有被加载过——浏览器要发网络请求、解析代码、执行完模块里的逻辑,这些步骤不可能一步到位。

  • import() 返回 Promise,resolve 值是命名空间对象
  • 模块可能需要网络请求和执行,所以必须异步
  • 路径错误或模块有语法问题,Promise 直接 reject
  • 同一模块多次 import() 只执行一次,后续走缓存

命名空间对象长什么样

假设有一个工具模块,导出了一个函数和一个常量。用 import() 加载之后,Promise resolve 的值就是一个对象,每个导出都是它的属性。

import() 的基本用法JavaScript
// 假设 utils.js 导出了 add 和 VERSION
// import('./utils.js') 返回 Promise

async function loadUtils() {
  const mod = await import('./utils.js')
  // mod 是命名空间对象,结构类似:
  // { add: Function, VERSION: '1.0', default: ... }

  console.log(mod.add(1, 2))  // 3
  console.log(mod.VERSION)     // '1.0'
  console.log(mod.default)     // 默认导出,没有则是 undefined
}

命名空间对象是冻结的,不能给它添加新属性,也不能修改已有属性的指向。不过如果导出的是一个对象,修改那个对象内部的属性是可以的——这跟 Object.freeze 的浅冻结同一个道理。 实际项目里最常见的写法是直接用解构拿需要的导出。比如 const { renderChart } = await import('./chart.js'),这样比通过 mod.renderChart 访问更简洁。

为什么不能像静态 import 一样同步拿结果

静态 import 写在文件顶部,引擎在解析阶段就知道要加载哪些模块。代码还没开始跑,依赖关系已经确定了,引擎可以在执行前把所有模块都准备好。

import() 不一样。它是一个运行时表达式,可以出现在 if 里、函数里、事件回调里。引擎在解析阶段没办法知道这行代码会不会执行、什么时候执行。执行到这行时,目标模块可能还在服务器上,所以必须返回一个 Promise,等加载完了再 resolve。

即使目标模块已经被加载过(比如之前某处用过 import() 加载了同一个路径),import() 仍然返回 Promise。引擎从模块缓存里直接取,不会重新下载和执行,但返回值类型始终是 Promise,这样调用方不用关心模块有没有被缓存过,写法始终一致。

加载失败怎么处理

路径不存在、网络超时、模块内部有语法错误,这些情况 import() 返回的 Promise 都会 reject。

用 try...catch 捕获错误JavaScript
async function loadChart() {
  try {
    const { renderChart } = await import('./chart.js')
    renderChart('#container')
  } catch (err) {
    // 路径错会报 TypeError
    // 模块语法错会报 SyntaxError
    console.error('图表模块加载失败', err)
    showFallback()
  }
}

这跟 fetch 请求失败的处理方式一样。如果不写 catch,Promise rejection 会变成未捕获异常。生产环境里动态加载的模块一般都要加降级逻辑,比如显示一个"加载失败,请刷新"的提示。 需要注意的是,一旦某个模块加载失败,下次再 import() 同一个路径,不会自动重试,而是直接返回上次的错误。想重试的话,需要给路径加上不同的查询参数来绕过缓存,比如 import('./chart.js?retry=1')。

回答前,多想一步

容易答错的地方

以为 import() 是函数
很多人说"调用 import 函数",其实 import() 是运算符,和 typeof x 类似。你不能写 const myImport = import 然后 myImport('./a.js'),也不能用 .call() 改变上下文。规范把它定义为运算符而不是函数,是为了让引擎能对它做特殊的静态分析优化。
以为返回的是默认导出
import() 返回的是整个命名空间对象,不是默认导出。想拿默认导出要写 (await import('./a.js')).default。常见错误是 const Component = await import('./Button.js') 然后直接当组件用,结果拿到的是一个有 default 属性的对象,渲染什么都出不来。
以为每次 import() 都会重新执行模块
模块只会被执行一次。第一次 import() 触发加载和执行,之后再 import() 同一个路径,引擎从缓存里取,直接返回上次的命名空间对象。如果模块里有副作用(比如 console.log('模块执行了')),只会打印一次。
试着用自己的话回答

面试官还会怎么问?

import() 能在 CommonJS 文件里用吗?

可以。从 Node.js 12 开始,CommonJS 文件里也能用 import() 来加载 ESM 模块。这是 CJS 和 ESM 互通的主要桥梁,因为 CJS 不支持静态 import,而 require() 又不能加载 ESM 文件。

import() 和 require() 的区别在哪?

require() 是同步的,模块代码在 require() 那一行就执行完了,返回的是 module.exports 的值拷贝。import() 是异步的,返回 Promise。另外 require() 拿到的是值的快照(对基本类型来说),而 import() 拿到的命名空间对象里的属性是实时绑定的——源模块改了值,这边读到的也跟着变。

Webpack 里 import() 是怎么做到代码分割的?

Webpack 在编译时把 import() 的目标模块及其依赖打成一个单独的 chunk 文件。运行时 import() 被替换成动态创建 <script> 标签加载该 chunk 的逻辑,加载完成后 resolve Promise。这就是路由懒加载的底层原理——每个路由对应一个 chunk,进入该路由时才下载。

多个 import() 可以并行加载吗?

可以。用 Promise.all 就行,比如 const [modA, modB] = await Promise.all([import('./a.js'), import('./b.js')])。两个模块会同时开始加载,总耗时取决于最慢的那个,而不是两者之和。

从一道题,走向一组知识

把知识连起来

模块与语言机制

动态 import() 里能用变量或模板字符串当路径吗?

import() 的参数可以用变量或模板字符串,但打包工具对动态路径有特殊限制。

模块与语言机制

什么时候该用动态 import() 而不是静态 import?

弄清 import() 的返回值后,接着看什么场景该用它替代静态 import。

参考资料

  • JavaScript:模块与语言机制参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 命名空间对象长什么样
  3. 为什么不能像静态 import 一样同步拿结果
  4. 加载失败怎么处理
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑