JavaScript 提升(Hoisting)与声明优先级:一篇文章说透

JavaScript 提升(Hoisting)与声明优先级:一篇文章说透


一、什么是提升(Hoisting)

JS 引擎在执行代码前会先扫描一遍,把声明提到作用域顶部。这就是"提升"。

关键:提升的是声明,不是赋值。

js 复制代码
console.log(a); // undefined(不是报错!)
var a = 1;

等价于:

js 复制代码
var a;          // 声明被提升
console.log(a); // undefined
a = 1;          // 赋值留在原地

二、函数声明 vs 变量声明:优先级铁律

核心规则(记住这一条就够了)

函数声明优先于变量声明。

同名时,函数声明覆盖变量声明。但后续的赋值可以覆盖函数。

验证代码

js 复制代码
console.log(foo); // [Function: foo]

var foo = 'bar';

function foo() {
  console.log('function');
}

console.log(foo); // 'bar'

为什么? 引擎处理顺序:

javascript 复制代码
1. 函数声明 foo 提升 → foo = function
2. 变量声明 foo 提升,但 foo 已存在 → 忽略 var 声明
3. 执行 console.log(foo) → function
4. 执行 foo = 'bar' → 赋值覆盖函数
5. 执行 console.log(foo) → 'bar'

三、三种声明方式的提升差异

声明方式 提升 初始化值 可重复声明
function 声明+函数体一起提升 函数体
var 只提升声明 undefined
let/const 提升但进入 TDZ 无(访问报错)
class 提升但进入 TDZ 无(访问报错)

四、var vs let/const 的 TDZ(暂时性死区)

js 复制代码
console.log(x); // ReferenceError
let x = 1;

let/const 也会提升,但从作用域顶部到声明行之间是 TDZ,访问就报错。

js 复制代码
let a = 1;

function fn() {
  console.log(a); // ReferenceError!不是 1
  let a = 2;
}

fn();

原因 :函数内部 let a 也被提升了,导致外层 a 被遮蔽,但又没到声明行,触发 TDZ。


五、函数表达式 vs 函数声明

js 复制代码
// 函数声明 --- 整体提升
foo(); // 能执行 ✅
function foo() {}

// 函数表达式 --- 只有变量提升
bar(); // TypeError: bar is not a function ❌
var bar = function() {};

提升后等价于:

js 复制代码
function foo() {}  // 整体提升
var bar;           // 只有声明提升 = undefined
foo();             // OK
bar();             // bar 是 undefined,不是函数
bar = function() {};

六、块级作用域中的奇怪行为

js 复制代码
console.log(foo); // undefined(不是函数!)

if (true) {
  function foo() { return 1; }
}

ES6 之前函数声明不能出现在块中(语法错误)。 ES6 之后:块中的函数声明被当作 类似 var 处理------声明提升到外层,但初始化为 undefined,进入块时才赋值。

严格模式下行为不同,不做展开,记住:别在块里写函数声明。


七、面试级综合题

js 复制代码
var a = 1;

function b() {
  a = 10;
  return;
  function a() {}
}

b();
console.log(a); // ?

答案:1

解析

  1. 函数 b 内部,function a() {} 被提升到 b 的作用域顶部
  2. a = 10 赋值的是函数 b 内部的局部 a,不是全局的那个
  3. 外层 console.log(a) 拿的还是全局的 1

js 复制代码
var foo = 1;

(function() {
  console.log(foo); // ?
  foo = 2;
  console.log(foo); // ?
  var foo = 3;
  console.log(foo); // ?
  console.log(window.foo); // ?
})();

答案:undefined → 2 → 3 → 1

解析 :IIFE 内部 var foo 被提升,遮蔽全局 foo。全过程只操作局部变量,window.foo 始终是 1


八、终极记忆口诀

  1. 函数优先:同名时函数声明 > var 声明
  2. 赋值为王:任何赋值操作都会覆盖之前的值(包括函数)
  3. var 是 undefined :提升后默认值是 undefined
  4. let 是死区:提升后在 TDZ,访问就炸
  5. 函数声明带身体:整个函数体一起提升
  6. 表达式不带身体:只有变量声明提升,函数体留原地

九、不要依赖提升

写代码时遵守两条规则,一辈子不踩坑:

js 复制代码
// ✅ 变量先声明再用
const foo = 1;
console.log(foo);

// ✅ 函数写在前面再调用
function doSomething() { /* ... */ }
doSomething();

理解提升是为了看懂别人的坑 ,不是为了给自己挖坑

相关推荐
IT_陈寒22 分钟前
Redis的KEYS命令把我搞崩溃了,改用SCAN才活过来
前端·人工智能·后端
Jackson__1 小时前
为什么 Agent 越聊越慢?聊聊 Context(上下文)管理
前端·agent·ai编程
KaMeidebaby1 小时前
卡梅德生物技术快报|抗体合成:多肽抗体合成工程化方案:Nsp2 保守肽多抗制备与多维度验证
前端·网络·数据库·人工智能·算法
青禾网络1 小时前
前端做音画匹配这件事,我从"随机塞"到"AI 自动对齐"
前端·github
2601_963771372 小时前
Hardening Enterprise WordPress Sites Against REST API Leaks and Bad Headers
前端·windows·word·php
蓝银草同学2 小时前
Stream 实战:博客列表排序、过滤与分页(AI 辅助学习 Java 8)
java·前端·后端
2601_957190902 小时前
飞行影院安装施工指南:场地、动感系统与影片内容配套
大数据·前端·人工智能
爱折腾的小黑牛2 小时前
简记往来批量录入功能的实现:从文本到结构化数据
前端·算法
YHHLAI3 小时前
Agent 智能体开发实战 · 第六课:MCP 协议 —— 让 Agent 跨进程调用工具
前端·人工智能
SmartBoyW3 小时前
前端死磕:一文彻底搞懂 JS 事件循环 (Event Loop) 与宏微任务
前端·javascript