← 返回目录

9. 预解析与变量函数提升

预解析就是JS 执行前先把 var 和 function 声明"搬"到最前面

核心规则:
var 声明提升——只提升声明不提升赋值,所以访问得 undefined
function 整体提升——函数声明可以先调用后定义
let/const 不提升——暂时性死区,访问报错

坑:var 声明前访问不报错得 undefined、let 声明前访问报 ReferenceError、函数表达式不会提升

9.1 互动演示(预测输出 + 预解析等价还原)

先自己猜输出,再点按钮看答案。

9.2 知识点逐条讲解(配合代码)

① 两步走:先搬家伙,再从上往下跑

① 预解析阶段:把当前范围里的 var 变量声明function 整台函数提前搬到最顶上;只搬「声明」,不搬「赋值和调用」。② 然后才从上往下真正执行。

② var 提升:只搬空盒子,不搬里面的东西

// 打印出来是 undefined,没报错
console.log(num);

// 预解析时其实相当于:
let num = 10;

如果你以为 let/const 也会被提前搬,就会搞混。因为 let/const 没有这种提升,它们有「暂时性死区」,声明前直接报错,不偷偷 undefined

③ function 整台提升:可以先调用后定义

// 还没写定义就喊,居然也行
fn();

// 因为整台 function 被提前装好了
function fn() {
  console.log("打印");
}

④ 函数表达式提升陷阱:只搬了个空变量名

// 报错:fn2 is not a function
fn2();

let fn2 = function () {
  console.log("想不到吧");
// 预解析只搬了 let fn2 这个空盒子
};

⑤ 两大陷阱

陷阱1:函数里写了 var,会盖住外面同名的。函数里只要出现 let num,整个函数内的 num 都用这个自己的,哪怕写在打印之后:

// 全局 num 是10
let num = 10;

fun();

function fun() {
  // 打印 undefined,不是10
  console.log(num);
  // 函数里有 let num → 整个函数都用这个,盖住全局
  let num = 20;
}

陷阱2:不写 var 直接赋值,东西漏成了全局。尤其是连等:

function f1() {
  // 其实拆开是:let a=9; b=9; c=9;
  let a = b = c = 9;
  // 只有 a 有 var,b、c 没写 var → 漏成了全局!
}

f1();

// 9 9(外面也能取到 b、c)
console.log(b, c);

// 报错:a 是函数内部的,外面没有
console.log(a);

⑥ X.3 实战:用在哪 / 常见坑 / 怎么解决

① 用在哪:解释「为什么声明前打印是 undefined」「为什么函数能提前调用」,面试常问。

② 常见坑:以为 let 也被提升;函数表达式提前调用报 not a function;连等 a=b=c=9 漏出全局。

③ 怎么解决:一律「先声明后使用」;不用 var 连等;看不懂输出就把代码按预解析规则手动还原一遍。

一句话记住:预解析=先搬声明再执行;var 只搬空盒子不搬赋值(提前用是 undefined);function 整台搬;函数表达式只搬变量名(提前喊报错);函数内 var 盖住同名;不写 var 的连等会漏成全局

本页重点

API作用参数返回值代码示例
var 提升声明被提前,赋值不提前变量名undefined
console.log(x);

let x = 1;  // 打印 undefined
函数声明提升整个函数提前,可先调用函数名可调用
f();

function f() {}
// 不报错
let 不提升声明前用会报错(TDZ)变量名报错
console.log(x);

let x = 1;  // 直接报错