前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库函数声明与函数表达式区别 提升
JavaScript函数与作用域

函数声明和函数表达式有什么区别?为什么一个能提前调用一个不能?

函数声明在进入作用域时就已经创建好了,所以写在后面也能先调用。函数表达式要等代码执行到那一行才创建,提前调用时变量还没有函数值:var 写法拿到 undefined 报 TypeError,let 和 const 写法直接报 ReferenceError。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#函数与作用域
先看核心答案读代码示例
理解线索

声明和表达式的三个差别

  1. 函数声明以 function 开头的语句,进入作用域时就创建好函数
  2. 函数表达式出现在赋值、传参等位置,执行到才创建函数
  3. 具名函数表达式const f = function inner() {} 里的 inner 只在函数体内可见

判断是声明还是表达式,只看 function 是不是出现在语句开头。(function () {})、!function () {} 里的 function 前面有别的符号,都是表达式。

核心回答

先记住这个答案

区别在于函数什么时候被创建。函数声明在进入作用域时就创建好了,名字和函数体一起提升,所以可以在声明之前调用。函数表达式是把一个函数赋给变量,只有变量声明会按 var、let、const 各自的规则提升,函数值要等执行到赋值那一行才赋上。所以 var 写法提前调用时变量是 undefined,报 TypeError;let 和 const 写法提前访问会报 ReferenceError。

  • 函数声明连名字带函数体一起提升
  • 函数表达式只提升左边的变量,函数值要执行到才赋上
  • var 提前调用报 TypeError,let 提前调用报 ReferenceError
  • 具名函数表达式的名字只在函数内部能用

三种写法提前调用的结果

把三种写法都放在调用语句后面,结果一目了然。

声明、var 表达式、let 表达式对比JavaScript
console.log(declared()) // 函数声明,可以提前调用

try {
  byVar()
} catch (e) {
  console.log(e.name) // byVar 此时是 undefined
}

try {
  byLet()
} catch (e) {
  console.log(e.name) // byLet 还在暂时性死区
}

function declared() { return 'ok' }
var byVar = function () { return 'var' }
let byLet = function () { return 'let' }
查看输出与解释
ok
TypeError
ReferenceError

declared 在代码开始执行前就已经是一个函数了。byVar 的变量声明被提升,初始值是 undefined,对 undefined 做调用就是 TypeError: byVar is not a function。byLet 的变量也登记了,但在 let 那一行执行前不允许访问,所以是 ReferenceError。 两种错误的含义不同:TypeError 说明变量存在但值不对,ReferenceError 说明变量根本不能访问。排查时看错误类型就能知道是哪种写法出的问题。

具名函数表达式的名字在哪能用

函数表达式也可以带名字,比如 const factorial = function fact(n) {}。这个 fact 和函数声明的名字不一样,它不会出现在外层作用域里,只在函数体内部可以用。

递归时用内部名字更可靠
在函数体里用 fact(n - 1) 递归,即使外面的 factorial 后来被重新赋值,或者这个函数被传到别处,内部的 fact 始终指向它自己。在外面写 fact(5) 会报 ReferenceError,因为外层没有这个名字。另外具名表达式在报错堆栈里显示的是 fact,排查问题更方便。
块里的函数声明别依赖
在 if 块里写函数声明,严格模式下它只在块内可见;非严格模式下为了兼容老代码,浏览器还会在外层创建一个同名 var,具体行为很绕。项目里需要条件定义函数时,直接用 const fn = cond ? a : b 这种表达式写法,就不会有歧义。
回答前,多想一步

容易答错的地方

说函数表达式完全不提升
左边的变量是会提升的,var 提升后值是 undefined,let 和 const 提升后处于暂时性死区。提升的是变量,没有提升的是函数值。
同名时以为后面的 var 会覆盖函数声明
var a; function a() {} 放在一起,提升阶段函数声明优先,只写了 var a 而没有赋值时,a 还是那个函数。但如果写成 var a = 1,执行到这一行时会被赋成 1。
试着用自己的话回答

面试官还会怎么问?

箭头函数算声明还是表达式?

箭头函数只能以表达式形式出现,必须赋给变量或作为参数传入,所以它遵循函数表达式的规则,不能提前调用。

export default function () {} 是声明还是表达式?

它是函数声明,可以没有名字,而且会被提升。它的 name 属性是 default。

项目里应该用哪种?

两种都常见。函数声明适合模块里的工具函数,写在文件底部也能在上面调用;const 加箭头函数适合回调和组件内部函数,能防止被重新赋值。团队统一一种风格,并用 ESLint 的 func-style 规则约束即可。

从一道题,走向一组知识

把知识连起来

函数与作用域

所有函数都有 prototype 吗?箭头函数为什么没有?

声明和表达式创建的都是普通函数,都有 prototype、能 new;箭头函数表达式就不行。

函数与作用域

生成器函数是怎么工作的?yield 和 next 是什么关系?

生成器同样有声明 function* gen() {} 和表达式两种写法,提升规则和普通函数一致。

参考资料

  • JavaScript:函数与作用域参考资料
  • function expression - JavaScript | MDN

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

本题目录
  1. 先记住这个答案
  2. 三种写法提前调用的结果
  3. 具名函数表达式的名字在哪能用
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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