在 JavaScript 中,同名函数(或变量与函数同名)的优先级问题,核心考察的是 变量提升 、函数提升 以及 执行上下文 的机制。
为了让你彻底弄懂,我将通过几道经典例题,配合"分析思路"为你详细讲解。
核心规则
三个核心规则:
- 函数提升优先级高于变量提升:如果同名,函数声明会覆盖变量声明(但不会覆盖变量已经被赋的值)。
- 函数声明会被整体提升 :包括函数体;而
var变量只提升声明,不提升赋值。 - 同名函数,后定义的覆盖先定义的:在提升阶段,后面的函数声明会覆盖前面的函数声明。
let/const不会与function重复声明 :如果作用域内有同名function,再用let/const声明会直接报错。
例题一:函数声明与变量声明同名
javascript
console.log(foo); // ?
var foo = 1;
function foo() {}
console.log(foo); // ?
输出结果:
javascript
[Function: foo]
1
分析思路:
-
第一步:变量/函数提升。 引擎在解析代码时,会将声明提到当前作用域顶部。 原本代码等价于:
javascriptfunction foo() {} // 函数提升,优先级高,先提升 var foo; // 变量提升,发现已有同名 foo,忽略声明,不覆盖 -
第二步:逐行执行。
- 第一行
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
分析思路:
-
第一步:提升。
javascriptfunction a() { console.log(2); } // 函数提升 var a; // 变量提升,遇到同名函数,声明被忽略 -
第二步:逐行执行。
- 第一行
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
分析思路:
-
第一步:提升。 遇到多个同名函数声明,后定义的覆盖先定义的 。 提升后的顺序等价于:
javascriptfunction foo() { console.log(1); } function foo() { console.log(3); } // 覆盖了前面的 var foo; // 忽略 -
第二步:逐行执行。
- 第一行
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
分析思路:
-
第一步:全局提升。 全局作用域中声明了
var b和function test。 -
第二步:执行
test(),进入函数作用域。 函数内部也会发生提升,且只作用于当前函数作用域 。内部提升后等价于:javascriptfunction test() { function b() {} // 内部函数提升 var b; // 内部变量提升,忽略 console.log(b); // 此时 b 是内部函数 b = 2; // 赋值 function b() {} // 提升阶段已处理,跳过 console.log(b); // 此时 b 是 2 } -
第三步:逐行执行内部代码。
- 第一个
console.log(b):由于函数内部有b的声明(发生了提升),所以不会去外部找全局变量b=1。内部b此时是函数,输出[Function: b]。 b = 2:赋值为 2。- 第二个
console.log(b):输出2。
- 第一个
总结:
- 找作用域: 确定代码是在全局作用域还是函数作用域内。
- 画提升(预编译):
- 先找所有的
function声明,提到最顶端。如果有多个同名function,后者覆盖前者。 - 再找所有的
var声明,提到最顶端。如果var的名字和function重了,直接删掉这个var声明(因为毫无影响)。 - 注意:
let/const不参与这种覆盖,如果它们和function同名,直接报语法错误SyntaxError: Identifier 'xxx' has already been declared。
- 先找所有的
- 从上到下执行:
- 遇到赋值语句(
xxx = yyy),就修改当前内存中xxx的值。 - 遇到
function声明语句,直接跳过(因为在提升阶段处理过了)。
- 遇到赋值语句(
- 输出:
- 严格按照执行顺序,读取当前变量在内存中的最新值进行输出。