块级作用域下函数声明的特殊机制

36 阅读2分钟

冲突发现

在学习执行上下文的时候我们学到:

  • 在函数执行的时候会创建函数的执行上下文
  • 执行上下文在创建阶段会确认 变量声明
  • 变量声明包括
    1. 函数的形参(需要明确赋值 没有就为undefined)
    2. arguments(需要明确赋值 没有就为undefined)
    3. 字面量函数声明(需要明确赋值)
    4. var形参(不需要赋值)

也就是说,会有下面的情况

function test(num) {
    console.log(num) // 明确赋值的形参
    console.log(arguments) // [Arguments] { '0': 1, '1': 2, '2': 3 } argument明确赋值
    console.log(inner) // [Function: inner] 明确赋值的函数
    console.log(a) // undefined 变量声明,没有赋值,所以为 undefined
    function inner() {
        console.log('inner')
    }
    var a = 2
}

test(1, 2, 3)

我们可以看到在函数内部会提前将字面量函数明确赋值,所以可以打印出inner函数

但是会有例外

function test(num) {
    console.log(num) // 明确赋值的形参
    console.log(arguments) // [Arguments] { '0': 1, '1': 2, '2': 3 } argument明确赋值
    console.log(inner) // undefined 
    console.log(a) // undefined 变量声明,没有赋值,所以为 undefined
    if (true) {
        function inner() {
            console.log('inner')
        }
    }
    var a = 2
}

test(1, 2, 3)

这里如果将函数放到块级作用域之后,打印函数为undefined

块级作用域下函数声明的特殊机制

本质:这个冲突的核心在于传统的执行上下文规则ES6块级作用域规则之间的矛盾

在反复权衡下,最终以ES6以块级作用域规则为准,但同时为了兼容性做出了复杂的妥协。

ES6后的双重提升机制

如果处于块级作用域,函数声明会提升到块级作用域的顶部和上级作用域顶部

function test() {
    console.log(foo); // undefined
    
    if (true) {
        console.log(foo); // undefined
        
        function foo() {
            return 'block function';
        }
        
        console.log(foo); // [Function: foo]
    }
    
    console.log(foo); // [Function: foo] - 函数被提升到外层作用域
}

历史下的妥协

在ES6之前,大多数JavaScript引擎将块中的函数声明提升到函数作用域顶部:

// ES5行为
function test() {
    if (false) {
        function foo() {
            return 'never executed';
        }
    }
    
    console.log(foo); // 在某些引擎中可能是 [Function: foo]
}

为了兼容之前的代码,es6不得不将函数的声明上去,但是不会赋值默认为undefined

最佳实践

不要在块级作用域里声明字面量函数