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();

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

相关推荐
七牛开发者1 小时前
不写框架、不用 npm,我用 AI Coding 做了一个家庭记忆站
前端·人工智能·npm
@PHARAOH1 小时前
WHAT - npm和corepack
前端·npm·node.js
不爱学英文的码字机器1 小时前
被 AE 的关键帧折磨过的人,应该试试这个用 React 写视频的路子
前端·react.js·音视频
Csvn1 小时前
组合式函数
前端·vue.js
CodeSheep1 小时前
中国编程第一人,一人抵一城!
前端·后端·程序员
GISer_Jing1 小时前
Claude Code项目配置终极指南
前端·ai·ai编程
MPGWJPMTJT1 小时前
从 Volta 迁移到 mise:Windows 下 Node 版本管理切换记录
前端·node.js
米丘1 小时前
vue3.x 调度器(Scheduler)实现机制
前端·javascript·vue.js