前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库var let 变量提升 暂时性死区
JavaScript函数与作用域

var 和 let 都会提升吗?为什么 let 在声明前访问会报错?

三种声明都会提升,区别在于提升后能不能读。var 提升时顺手初始化成 undefined,声明前读到的就是 undefined;let 和 const 只登记名字不初始化,声明语句执行之前去读,直接抛 ReferenceError,这段时间叫暂时性死区。

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

提升分两步:登记名字和初始化

  1. var进入函数时登记并初始化为 undefined,作用域是整个函数
  2. let / const进入块时登记,执行到声明那一行才初始化,作用域是所在的块
  3. 暂时性死区从进入作用域到声明语句执行完之间,读写这个变量都会抛错

函数声明是整体提升的,声明之前就能调用;class 声明和 let 一样有暂时性死区,声明前 new 会报错。

核心回答

先记住这个答案

var、let、const 都会提升,引擎在进入作用域时就把这些名字登记好了。区别在初始化:var 登记时就被赋成 undefined,所以声明前读取不报错,只拿到 undefined。let 和 const 登记后保持「未初始化」状态,要等执行到声明那一行才可用,在这之前读写都会抛 ReferenceError。这段不能访问的区间叫暂时性死区(TDZ)。

  • 三种声明都会提升,只是初始化时机不同
  • var 声明前读取得到 undefined
  • let 和 const 声明前读取抛 ReferenceError
  • 暂时性死区里连 typeof 都会报错

声明前读取三种变量

在同一个函数里,把三种变量的读取都写在声明之前,看各自的表现。

声明前分别读 var、let、constJavaScript
function inspect() {
  console.log(a) // var:不报错
  try {
    console.log(b)
  } catch (err) {
    console.log(err.name) // let:抛错
  }
  try {
    console.log(typeof c) // const:typeof 也救不了
  } catch (err) {
    console.log(err.name)
  }
  var a = 1
  let b = 2
  const c = 3
}
inspect()
查看输出与解释
undefined
ReferenceError
ReferenceError

var a 在函数开始执行时就已经是 undefined,赋值 a = 1 还留在原位置,所以第一行打印 undefined。b 和 c 名字已经存在,但没初始化,读取直接报错。 第三个 try 值得多看一眼:很多人以为 typeof 永远不会抛错,对完全没声明过的变量确实如此,它返回字符串 'undefined'。但变量在暂时性死区里时,typeof 一样会抛 ReferenceError。

为什么说 let 也提升了

面试里常有人说「let 不会提升」,这不准确。最直接的证据是遮蔽:块里一旦声明了同名 let,从块的第一行开始,这个名字指的就是块里的变量,外层同名变量已经读不到了。

内层的 let 挡住了外层变量JavaScript
let name = 'outer'
{
  try {
    console.log(name) // 这里读的是下面那个 name,还没初始化
  } catch (err) {
    console.log(err.name)
  }
  let name = 'inner'
  console.log(name)
}
查看输出与解释
ReferenceError
inner

如果 let 真的不提升,第一次读取应该拿到外层的 'outer'。实际却抛错了,说明进入块的时候,内层的 name 就已经登记好了,只是还不能用。

暂时性死区是按时间算的
死区指的是「执行到声明之前」这段时间,不是代码里声明上面那几行。比如在函数里读取一个外面的 let 变量,只要函数是在变量声明之后才调用的,就没问题,哪怕函数定义写在声明上面。反过来,声明之前就调用这个函数,就会报错。

函数声明和 class 怎么提升

函数声明是整体提升的,名字和函数体一起就位,所以在声明之前调用完全没问题,这也是很多老代码把工具函数写在文件末尾的原因。

函数表达式要看左边用什么声明。var fn = function () {} 提升的只是 var fn,声明前调用 fn() 得到的是 TypeError: fn is not a function,因为此时 fn 是 undefined。换成 const fn = ...,声明前调用就是 ReferenceError。

class 声明和 let 一样有暂时性死区。声明前写 new User() 会抛 ReferenceError,不会像函数声明那样提前可用。

回答前,多想一步

容易答错的地方

说 let 和 const 不会提升
它们会提升,只是没有初始化。能说出「登记和初始化是两步」,再用遮蔽的例子证明,这个问题就答全了。
以为 typeof 能安全检测任何变量
typeof 只对从没声明过的名字安全。同一作用域后面有 let 或 const 声明时,声明前的 typeof 会抛错。
以为顶层 let 也会挂到 window 上
在浏览器脚本的顶层,var 声明的变量会变成 window 的属性,let 和 const 不会,它们放在一个单独的全局词法作用域里。在 ES 模块里,两者都不会挂到 window 上。
试着用自己的话回答

面试官还会怎么问?

暂时性死区有什么好处?

声明前误用变量会立刻报错,而不是悄悄拿到 undefined,然后在很远的地方出现诡异的计算结果。报错位置就是出问题的位置,排查快很多。

const 声明的对象能改属性吗?

能。const 只保证这个变量不能再指向别的值,对象内部的属性照样能改。想让对象不可修改,要用 Object.freeze,而且它只冻结第一层。

var 在 if 块里声明,块外能读到吗?

能。var 只认函数作用域,if 和 for 的花括号拦不住它。哪怕 if 条件是 false、那一行根本没执行,变量也已经提升到函数顶部,读到的是 undefined。

从一道题,走向一组知识

把知识连起来

函数与作用域

什么是 IIFE?现在有了 let 和模块,它还有什么用?

在没有 let 的年代,要给 var 一个独立的作用域只能靠 IIFE 包一层函数。

函数与作用域

箭头函数写的 IIFE 和普通函数写的 IIFE 有什么区别?

箭头函数写的 IIFE 同样能隔离 var,隔离变量靠的是函数作用域,和函数种类无关。

参考资料

  • JavaScript:函数与作用域参考资料

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

本题目录
  1. 先记住这个答案
  2. 声明前读取三种变量
  3. 为什么说 let 也提升了
  4. 函数声明和 class 怎么提升
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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