函数/变量提升

在 JavaScript 中,同名函数(或变量与函数同名)的优先级问题,核心考察的是 变量提升 、函数提升 以及 执行上下文 的机制。

为了让你彻底弄懂,我将通过几道经典例题,配合"分析思路"为你详细讲解。


核心规则

三个核心规则:

  1. 函数提升优先级高于变量提升:如果同名,函数声明会覆盖变量声明(但不会覆盖变量已经被赋的值)。
  2. 函数声明会被整体提升 :包括函数体;而 var 变量只提升声明,不提升赋值。
  3. 同名函数,后定义的覆盖先定义的:在提升阶段,后面的函数声明会覆盖前面的函数声明。
  4. let / const 不会与 function 重复声明 :如果作用域内有同名 function,再用 let/const 声明会直接报错。

例题一:函数声明与变量声明同名

javascript 复制代码
console.log(foo); // ?
var foo = 1;
function foo() {}
console.log(foo); // ?

输出结果:

javascript 复制代码
[Function: foo]
1

分析思路:

  1. 第一步:变量/函数提升。 引擎在解析代码时,会将声明提到当前作用域顶部。 原本代码等价于:

    javascript 复制代码
    function foo() {} // 函数提升,优先级高,先提升
    var foo;          // 变量提升,发现已有同名 foo,忽略声明,不覆盖
  2. 第二步:逐行执行。

    • 第一行 console.log(foo):此时 foo 是一个函数,输出 [Function: foo]。
    • 第二行 foo = 1:执行赋值操作,此时 foo 被赋值为数字 1。
    • 第三行 console.log(foo):输出 1。

例题二:函数声明与函数表达式同名

javascript 复制代码
var a = 1;
function a() {
    console.log(2);
}
console.log(a); // ?

输出结果:

javascript 复制代码
1

分析思路:

  1. 第一步:提升。

    javascript 复制代码
    function a() { console.log(2); } // 函数提升
    var a;                            // 变量提升,遇到同名函数,声明被忽略
  2. 第二步:逐行执行。

    • 第一行 a = 1:赋值操作,把函数 a 覆盖成了数字 1。
    • 第二行 function a(){...}:这行代码在提升阶段已经处理完毕,执行阶段直接跳过。
    • 第三行 console.log(a):输出 1。

例题三:同名函数声明的覆盖

javascript 复制代码
console.log(foo); // ?
function foo() { console.log(1); }
var foo = 2;
function foo() { console.log(3); }
console.log(foo); // ?

输出结果:

javascript 复制代码
[Function: foo] (输出 3 的那个函数)
2

分析思路:

  1. 第一步:提升。 遇到多个同名函数声明,后定义的覆盖先定义的 。 提升后的顺序等价于:

    javascript 复制代码
    function foo() { console.log(1); }
    function foo() { console.log(3); } // 覆盖了前面的
    var foo; // 忽略
  2. 第二步:逐行执行。

    • 第一行 console.log(foo):输出提升后的最终函数(即输出3的那个函数体)。
    • 第二行 foo = 2:赋值操作,foo 变为数字 2。
    • 第三、四行:跳过。
    • 第五行 console.log(foo):输出 2。

例题四:进阶!函数内部的同名提升(考察作用域)

javascript 复制代码
var b = 1;
function test() {
    console.log(b); // ?
    var b = 2;
    function b() {}
    console.log(b); // ?
}
test();

输出结果:

javascript 复制代码
[Function: b]
2

分析思路:

  1. 第一步:全局提升。 全局作用域中声明了 var b 和 function test。

  2. 第二步:执行 test(),进入函数作用域。 函数内部也会发生提升,且只作用于当前函数作用域 。内部提升后等价于:

    javascript 复制代码
    function test() {
        function b() {}   // 内部函数提升
        var b;            // 内部变量提升,忽略
        
        console.log(b);   // 此时 b 是内部函数
        b = 2;            // 赋值
        function b() {}   // 提升阶段已处理,跳过
        console.log(b);   // 此时 b 是 2
    }
  3. 第三步:逐行执行内部代码。

    • 第一个 console.log(b):由于函数内部有 b 的声明(发生了提升),所以不会去外部找全局变量 b=1。内部 b 此时是函数,输出 [Function: b]。
    • b = 2:赋值为 2。
    • 第二个 console.log(b):输出 2。

总结:

  1. 找作用域: 确定代码是在全局作用域还是函数作用域内。
  2. 画提升(预编译):
    • 先找所有的 function 声明,提到最顶端。如果有多个同名 function,后者覆盖前者。
    • 再找所有的 var 声明,提到最顶端。如果 var 的名字和 function 重了,直接删掉这个 var 声明(因为毫无影响)。
    • 注意:let/const 不参与这种覆盖,如果它们和 function 同名,直接报语法错误 SyntaxError: Identifier 'xxx' has already been declared。
  3. 从上到下执行:
    • 遇到赋值语句(xxx = yyy),就修改当前内存中 xxx 的值。
    • 遇到 function 声明语句,直接跳过(因为在提升阶段处理过了)。
  4. 输出:
    • 严格按照执行顺序,读取当前变量在内存中的最新值进行输出。
相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万3 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋3 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁3 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95273 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大3 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师3 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学3 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端