JS解释器中var/let声明函数的方式及变量提升相关疑问
函数声明 vs 函数表达式:变量提升导致的执行差异解析
嘿,这个问题抓得特别准!你观察到的执行结果差异,核心原因就是函数声明和函数表达式在变量提升(hoisting)机制上的本质区别,咱们一步步拆解:
一、函数声明的“全量提升+覆盖”逻辑
JS引擎在执行代码前,会先对当前作用域内的所有函数声明做“全量提升”——也就是说,整个函数的定义(包括函数体)都会被提升到作用域的最顶部。而且如果有多个同名的函数声明,后面的声明会直接覆盖前面的。
拿你的例子来说,实际执行顺序是这样的:
// 引擎预处理阶段:提升所有函数声明,后声明的覆盖前一个 function myFunction(){ console.log("7"); }; function myFunction(){ console.log("3"); } // 这个定义被上面的覆盖了,相当于不存在 // 进入代码执行阶段 myFunction(); // 此时调用的是被提升后保留的最后那个函数,输出"7" myFunction(); // 同样调用最后那个函数,输出"7"
所以不管你把函数声明写在调用语句的前面还是后面,引擎都会先把所有函数声明捞到顶部,并且用最后一个定义覆盖前面的,这就是为什么两次调用都输出7。
二、函数表达式的“变量提升+延迟赋值”逻辑
而用var定义的函数表达式,本质是变量声明+函数赋值的组合。JS引擎只会提升var声明的变量名,不会提升后面的函数赋值部分——赋值操作要等到代码执行到那一行才会完成。
你的例子实际执行顺序是:
// 引擎预处理阶段:只提升var变量的声明,不提升赋值 var myFunction; // 进入代码执行阶段 myFunction = function(){ console.log("3"); } // 此时变量才被赋值为第一个函数 myFunction(); // 调用第一个函数,输出"3" myFunction = function(){ console.log("7"); }; // 变量被重新赋值为第二个函数 myFunction(); // 调用第二个函数,输出"7"
这就是为什么两次调用输出不同——赋值是按代码顺序执行的,每次调用时变量指向的是当前已经赋值的函数。
三、用var/let声明函数的常见方式
除了你例子里的匿名函数表达式,还有几种常见的写法:
1. var声明函数表达式
- 匿名函数表达式:
var myFunc = function() { /* 逻辑 */ } - 命名函数表达式:
var myFunc = function namedFunc() { /* 逻辑 */ }
命名的好处是可以在函数内部递归调用自己,而且这个namedFunc不会污染外部作用域,只在函数内部有效。
2. let声明函数表达式
let和var的核心区别是块级作用域和暂时性死区:
- 基本写法:
let myFunc = function() { /* 逻辑 */ } - 箭头函数写法:
let myFunc = () => { /* 逻辑 */ }(更简洁,适合短逻辑) - 注意:
let不允许在同一个作用域内重复声明同名变量,而且在声明前调用会触发ReferenceError(因为暂时性死区),这比var的“变量提升后默认undefined”更严谨。
现在主流开发中更推荐用let或const(如果函数不需要重新赋值的话)来声明函数表达式,避免var带来的作用域混淆问题。
内容的提问来源于stack exchange,提问作者Abdur Rahaman
相关产品推荐
相关产品推荐

