对块作用域与变量函数提升再添新认识

关于这篇博客

这篇博客是在我读《你不知道的JavaScript-上卷》的时候,遇到的我觉得需要记录下来的知识。

刚好又能够配合之前我写的这篇执行上下文与执行上下文栈博客中关于变量提升与函数提升知识,可以认为是对其的补充或新的认识吧,那么本篇开始!

回忆块与块级作用域

块我这里指的是代码块,在我学JavaScript无论是初级部分还是高级部分的时候,我都被教或者自己认为, {}对普通代码(除函数、类外)是没有任何影响的,至少在ES6以前是这样子的

var a = 666
{
    console.log(a)  // 666
    function foo () {
        console.log('foo()')
    }
    var myName = 'Fitz'

}
foo()   // 'Foo()'
console.log(myName) // 'Fitz'

看起来没有任何影响,一切都是那么的和谐对吧,但事情就出在了我在测试变量、函数提升与块级作用域配合时

直接附上

console.log(a)  // 猜猜是啥
foo()           // 猜猜是啥
{
    var a = 666
    function foo () {
        console.log('foo()')
    }
}

根据我以往的认识,{}在这是不会影响任何东西的,所以根据执行上下文与执行上下文栈所造成的的变量提升与函数提升,这个代码最终执行的结果应该是:

console.log(a)  // undefined
foo()           // 'foo()'
{
    var a = 666
    function foo () {
        console.log('foo()')
    }
}

可真正的执行结果是:

console.log(a)  // undefined
console.log(foo)    // 'undefined'
foo()           // TypeError: foo is not a function
{
    var a = 666
    function foo () {
        console.log('foo()')
    }
}

这就说明{}在这起作用了,最起码它阻挡了foo函数的提升,但是这个阻挡又是不全面的,至于为什么我这么认为,等等再说,先看看当前是怎么回事

由于var符合之前的知识,所以这里我们就不再关心了

根据《你不知道的JavaScript-上卷》书上说道,造成foo()提前运行报错,而不像之前执行上下文与执行上下文栈表现的那样,是因为一个普通块内部的函数申明会被提升到所在作用域(这里就是这个块级作用域)的顶部, 虽然不保证后续是否还会这样,但是至少到ES10了还是这样,所以记录一下

所以上面的代码是这样的

console.log(foo)    // 'undefined'
foo()           // TypeError: foo is not a function
{
    // 限制在这
    function foo () {
        console.log('foo()')
    }
}
foo()   // 'foo()' 可以正常调用

那我前面为什么说这个阻挡又是不全面的,这个是书上没有解释的地方,我也找不到解释或别的答案,导致我十分的困惑

阻挡不全面是因为报错的原因是TypeError而不是ReferenceError, TypeError的原因我们可以看到,是因为foo的值是undefined

console.log(foo)    // 'undefined'
foo()   // TypeError: foo is not a function

这说明在全局作用域中的那一刻是有这个变量的,只不过还没被赋值为函数,这种状态就好像是这样(结合变量提升与函数提升的知识):

foo()   // TypeError: foo is not a function
var foo = function () {
    console.log('foo()')
}

如果块作用域前调用的报错是ReferenceError这就完完全全表示那一刻没有foo这个变量

区别TypeError而不是ReferenceError

console.log(a)  // `ReferenceError` 根本没申明过这个变量
var b = 1
b()             // `TypeError` 有这个变量名,但是使用类型上错误

写在最后

不管怎样,虽然对这个现象很困惑,但还是将它记录下来说不定以后就懂了,但不管原因是怎样的,别这么写就对了,何必难为自己,难为别人呢,对吧!

// 请一定不要这么写,除非他是你的仇人
{
    function foo () {
        console.log('foo()')
    }
}
foo()   // 'foo()'


// 还算正常关系
foo()
function foo () {
    console.log('foo()')
}


// 跟他情深深
function foo () {
    console.log('foo()')
}
foo()