JavaScript中if-else与for循环内声明的函数不会被提升吗?
首先说明:实际开发中我们要完全避免这类写法,不要给不同函数声明相同的名称,也不要再使用var声明变量,否则会引发诸多问题,此处仅探究该现象背后的原理。
前置基础:var变量的提升逻辑
我们知道,使用var关键字声明的变量和函数声明,会在创建阶段被提升至所属函数作用域的顶部,我们先看变量的示例:
function f(){ console.log(p) // undefined if(true){ var p = true; } } f()
上述代码输出undefined,是因为if块没有独立的执行上下文,块内声明的变量会被提升至作用域顶部,底层实际运行逻辑等价于:
function f(){ // under the hood var p = undefined; console.log(p) // undefined if(true){ p = true; } } f()
这个逻辑是通顺的,接下来我们看函数的示例:
不符合预期的块内函数声明提升现象
function f(){ let num = 5; if(num < 10){ function a(){ console.log('less than 10') } a() } else if(num < 20){ function a(){ console.log('less than 20') } a() } else { function a(){ console.log('something else') } a() } } f()
该代码实际输出"less than 10",这是为什么?
我原本预期的提升逻辑是这样的:
function f(){ /* HOISTING function a(){ console.log('something else') } // HOISTING COMPLETED */ let num = 5; if(num < 10){ a() } else if(num < 20){ a() } else { a() } } f()
按常规逻辑,每个if-else块中都有同名函数a,最后声明的函数应该被提升,不管num的值是什么都会生效才对。
我们也知道,如果在同一作用域声明多个同名函数,最后一个会生效,因为它是最后被提升的,如下示例:
function f(){ b() // false function b(){ console.log(true); } function b(){ console.log(false); } } f()
那为什么if-else场景下逻辑不符合预期?
for循环也存在同样的情况:for循环没有独立的执行上下文,使用var声明的变量和函数声明理论上也应该被提升到外层函数作用域顶部,但以下代码无法正常运行:
function f(){ ff(); for (let i=0; i<1; i++){ function ff(){ console.log(true); } } } f();
我原本预期for循环内部的ff函数会被提升到外层f函数的顶部,但实际并没有。
再次说明:我清楚声明同名函数、在声明前调用函数、使用var关键字都是不良开发实践,此处仅为深入探究JS底层执行机制。
底层原因解释
这个现象本质是ES5和ES6规范对块级作用域内的函数声明的处理规则差异导致的:
- ES6之前的规范根本不允许在块内写函数声明,只是各浏览器私自做了非标准的兼容实现,不同浏览器的运行结果甚至都不统一
- ES6之后才正式规定了块内函数声明的行为:函数声明的作用域是它所在的块,而不是外层的函数作用域;块内的函数声明会被提升到块的顶部,而非外层函数的顶部
- 为了兼容大量旧代码,浏览器的实现额外加了特殊逻辑:会在外层作用域声明一个同名的变量,默认值为undefined,只有当代码执行进入到函数声明所在的块时,才会把这个外层变量赋值为块内的函数
对应到问题中的两个异常场景就很好理解了:
- if-else同名函数问题:三个
a函数分别属于三个独立的if/else if/else块,只有匹配成功的num <10块会被执行,进入块时才会把外层的a变量赋值为输出less than 10的函数,另外两个块从未执行,对应的函数声明自然不会生效,最终输出符合实际运行块的结果。 - for循环内函数无法提升问题:
ff函数的作用域是for循环的块,外层调用ff()时循环还未执行,外层的同名ff变量还是默认的undefined,调用就会抛出ff is not a function的错误,和实际运行结果完全一致。
正是因为块内函数声明的兼容逻辑极其混乱,才会出现各种反直觉的现象,实际开发中永远不要在块内写函数声明,如果有需要可以用函数表达式代替,比如const a = function(){},它的行为完全符合块级作用域的规则,不会有兼容问题。
内容的提问来源于stack exchange,提问作者imheretolearn1

