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

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

相关推荐
Hilaku9 分钟前
多标签页并发请求导致 Token 刷新失败?只有 15行代码就能解决 !
前端·javascript·程序员
Nile18 分钟前
解密Palantir系列一:4. Ontology 不是哲学
开发语言·前端·javascript
因_崔斯汀36 分钟前
ECharts 区域地图可视化实战:以山东地图为例
前端
Bacon44 分钟前
手摸手带你搞清楚 AI Agent 的六大核心概念
前端·人工智能
王林不想说话1 小时前
TypeScript 进阶知识总结:从 extends、泛型到 infer,一篇打通 TS 类型系统
前端·javascript·typescript
罗超驿1 小时前
15.JavaScript 函数与作用域完全指南:语法、参数、表达式与作用域链实战
开发语言·前端·javascript
.千余1 小时前
【C++】C++类与对象2:C++构造函数、运算符重载与流输入输出全面解析
c语言·开发语言·前端·c++·经验分享
星栈2 小时前
Rust 单二进制部署,真没你想的那么“单”
前端·后端
angerdream2 小时前
Android手把手编写儿童手机远程监控App之webrtc聊天数据通道
前端
浩风祭月2 小时前
受够了每次切分支都要重装依赖:一份 Git 工作流优化指南
前端·ai编程