前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库export default 表达式与声明区别
JavaScript模块与语言机制

export default 后面跟表达式和跟声明,有什么区别?

export default 后面跟表达式(比如一个变量名或立即执行函数),会立刻求值把当前值作为默认导出,后续变量改了导出不会变。跟函数声明或类声明时,声明本身会被提升,函数名在模块内部可用,且和默认导出指向同一个东西。

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

默认导出的两种形式

  1. 表达式形式export default expr,求值后绑定到 default,不跟踪后续变化
  2. 声明形式export default function foo(){},foo 被提升,模块内可用
  3. export { x as default }把具名变量映射为默认导出,保留实时绑定

export default class {} 这种匿名类声明在 new 的时候 .name 属性是 "default",调试时不太方便。

核心回答

先记住这个答案

export default 后面跟的是表达式还是声明,决定了两件事:有没有提升,以及有没有实时绑定。跟表达式时(比如 export default someVar 或 export default (function() {})()),引擎立刻对表达式求值,把当前值作为默认导出。跟函数或类声明时(比如 export default function foo() {}),声明本身会被提升,foo 在模块内可以在声明之前使用。

  • export default 加表达式:立刻求值,不是实时绑定
  • export default function foo(){} 有提升,foo 在模块内可用
  • 想要默认导出保持实时绑定,用 export { x as default }
  • 匿名函数或类声明的默认导出在调试堆栈里没有名字

表达式形式:求值后就定了

当 export default 后面跟一个变量名、字面量或者任意表达式时,引擎会在执行到这一行时对表达式求值,把得到的值作为默认导出。之后即使源模块修改了这个变量,默认导出的值不会变。

表达式形式没有实时绑定JavaScript
// config.mjs
let mode = 'development'

export default mode
// 默认导出是 'development' 这个字符串值

export function setProduction() {
  mode = 'production'
}

// main.mjs
// import config from './config.mjs'
// import { setProduction } from './config.mjs'
// console.log(config)    // 'development'
// setProduction()
// console.log(config)    // 还是 'development' —— 没有实时绑定

export default mode 在执行时把 mode 的当前值 'development' 取出来,绑定到 default 导出上。后面 mode 变成了 'production',但 default 导出已经和 mode 脱钩了。 这是很多人踩过的坑:以为 export default someVar 和 export { someVar as default } 效果一样,其实不一样。

声明形式:有提升也有本地绑定

export default function foo() {} 和 export default class Bar {} 是声明形式。函数声明会被提升到模块顶部,foo 在模块内部可以在声明之前使用。同时 foo 和默认导出指向同一个函数,不存在脱钩的问题。

但要注意,如果是匿名声明,比如 export default function() {} 或 export default class {},模块内部没有可以引用的名字。函数的 .name 属性会是 'default',在调试堆栈里不太好辨认,建议尽量给个名字。

想要默认导出保持实时绑定的写法

如果确实需要默认导出跟着源变量一起变化,用导出列表的写法。

export { x as default } 保留实时绑定JavaScript
// counter.mjs
let count = 0
export function increment() { count++ }

// 这种写法:默认导出是 count 变量的实时绑定
export { count as default }

// main.mjs
// import count from './counter.mjs'
// console.log(count)   // 0
// increment()
// console.log(count)   // 1 —— 实时绑定生效了

export { count as default } 建立的是一个实时绑定,和具名导出的 export { count } 机制完全一样,只是导出名变成了 default。这样导入方用 import count from './counter.mjs' 就能跟踪到源模块的变化。 这个写法在日常项目里很少用到。大多数情况下,默认导出的是组件、类或工厂函数,不需要实时绑定。

回答前,多想一步

容易答错的地方

以为 export default function 和 export default 加函数表达式一样
export default function foo() {} 是声明,foo 被提升,模块内可用。export default (function foo() {}) 是表达式(外面套了括号),foo 只在函数内部可用,不会被提升。写法差一个括号,行为完全不同。
以为 export default someVar 之后改 someVar 还能影响导出
不能。export default someVar 在执行那一刻就取了 someVar 的值,之后 someVar 怎么变都和默认导出无关了。想要实时绑定,必须用 export { someVar as default }。
试着用自己的话回答

面试官还会怎么问?

export default 42 合法吗?

合法。任何表达式都可以放在 export default 后面,42、'hello'、[1, 2, 3]、null 都行。但 export 42 不行——没有 default 的 export 后面只能跟声明或导出列表。

export default 能出现多次吗?

不能。一个模块只能有一个 default 导出,写两个 export default 会报 SyntaxError。如果需要导出多个值,要么用具名导出,要么把多个值包在一个对象里做默认导出(但这会影响 tree-shaking)。

为什么 React 社区习惯用 export default 导出组件?

因为一个组件文件通常就只导出一个组件,用 export default 导入时不用写大括号,代码更简洁。import Button from './Button' 比 import { Button } from './Button' 少一层大括号。不过也有不少团队转向了具名导出,因为它对 IDE 重构、搜索和自动导入更友好。

用 TypeScript 的 isolatedModules 时有什么影响?

开启 isolatedModules 后,不能对一个类型做 export default。比如 export default interface Foo {} 会报错,因为 TypeScript 在单文件编译时无法判断这个默认导出是值还是类型。解决办法是先用 interface Foo {} 声明,再用 export { type Foo as default } 导出。

从一道题,走向一组知识

把知识连起来

模块与语言机制

给 import 进来的变量重新赋值会怎样?

理解默认导出的绑定机制后,再看导入绑定只读的规则就能解释为什么不能给默认导入赋值。

模块与语言机制

export { a } 和 export const a 同时写会怎样?怎么避免命名冲突?

导出列表 export { a as default } 和声明导出 export default a 在实时绑定上的差异,直接影响跨模块的数据流。

参考资料

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

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

本题目录
  1. 先记住这个答案
  2. 表达式形式:求值后就定了
  3. 声明形式:有提升也有本地绑定
  4. 想要默认导出保持实时绑定的写法
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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