先看题目

function bs(){
    console.log(1);
}
function fn(){
    bs();
    if(false){
        function bs(){
            console.log(2)
        }
    }
}
fn();

表面上,这是一道js函数变量提升的问题,函数内输出2。真的是这样吗?
不是的,本题在当前浏览器下输出为bs is not a function错误
bs.png
(点击直接跳转到结论)

原因分析

这道题直觉上反常识,似乎fn中bs函数的作用域没有提升,因此这里的作用域一定出了问题,首先猜测是浏览器问题,因此分别测试了ES5和ES6环境

ES5环境

如果在ES5环境(IE浏览器选择低版本ES5环境)下,本题输出2,函数定义会提升到fn函数作用域顶部:

function bs(){
    console.log(1);
}
function fn(){
    function bs(){
        console.log(2)
    }
    bs(); // 2
    if(false){}
}
fn();

ES6环境

根据阮一峰ES6教程“ES6规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用”,即其执行效果是这样的:

function bs(){
    console.log(1);
}
function fn(){
    var bs = undefined;
    bs();
    if(false){
       function bs() { console.log(2) }
    }
}
fn();

其行为类似于ES6块级作用域中let定义前的暂时性死区

(function a() {
    console.log(b) // b is not defined
    let b;
})();

重大误区

错误理解ES6作用域

因为本题的执行结果如同函数bs的声明没有提升,容易误以为是if条件的花括号限制导致,进而认为ES6块级作用域是用花括号创建的,从而认为if条件的花括号构成块级作用域,并用以下代码测试得到“正确”结果

function bs(){
    console.log(1);
}
function fn(){
    bs();
    {    
        function bs(){
            console.log(2)
        } 
    }
    
}
fn();

实际上,这种歪打正着是非常要命的,ES6并未提到有花括号直接构成块级作用域的说法,ES6的块级作用域只能是花括号+let/const定义共同构建的,两者缺一不可

结论

  • 本题是ES6环境下函数作用域中声明函数的坑
    • ES5浏览器环境下输出2,是浏览器自行实现的结果。
    • 根据规范,ES5的函数中不应使用函数声明,但是根据浏览器的实现:在函数中使用函数声明则声明会提升
    • ES6的函数中允许使用函数声明,但其行为类似于let定义,有暂时性死区的效果
  • 无论是ES5还是ES6,都不应该在函数作用域中写函数声明,写成函数表达式更容易理解
  • 这道题反映了浏览器实现、JS语言规范的问题,提醒自己要重视规范,遇到问题要刨根问底从规范着手解决问题,只有阅读规范才能更深入理解语言细节。