神奇的变量提升

142 阅读3分钟

在ES6出来以前,不存在块级作用域

以下举个小小的栗子

    var a = 1;
    if ( true ) {
        var a = 2;
    }
    console.log(a) //2
    {
        var a = 3;
    }
    console.log(a) //3

可以看到,无论是在if代码块还是代码块中都成功给全局变量 a 赋值了,而函数同样也会变量提升,下面让我们来看看这些骚操作吧

神奇的变量提升(代码块篇)

    var a = 1;
    {
        a = 2;
        function a() {}
        a = 3;
        
        console.log(a); //3
    }
    console.log(a); //2

是不是亿脸懵逼,最后这个console打印的怎么是个 2 ?

我来讲解下,发生了什么情况:

  • 首先 a 函数声明,由于变量提升,此时全局变量 a = function() {}。
  • a = 2 覆盖 a 函数,导致全局变量上的 a = 2,并切断了引用关系。
  • a = 3 时,由于 a = 2 切断了引用关系,所以在 a = 2 之后代码不再改变全局变量 a 。
  • 执行代码块内的 console 打印出 3
  • 执行全局的 console 打印出 2

然后再说下如果函数已经置顶会做什么骚操作:

    var a = 1;
    {
        function a() {}
        a = 2;
        a = 3;
        
        console.log(a); //3
    }
    console.log(a); //function a() {}

此时打印出来又不同了,哎,这东西真难。

没关系,我们对比之前的代码,再深入研究发现:

  • 首先还是 a 函数声明,由于变量提升,此时全局变量 a = function() {}。
  • 然后,a = 2 覆盖代码块内 a 的值,并切断了引用关系。注意,这里的 a 是先切断联系再赋值,而之前的是先赋值再切断联系。

剩下的和之前的差不多就不多说了

神奇的变量提升(函数篇)

好了,既然代码块内变量提升了一遍了,函数内作用域变量提升也过一遍吧

    var a = 1;
    !(function b() {
        a = 2
        function a() {}
        console.log(a); //2
    })();
    console.log(a);//1

我们来看看这里又搞了什么骚操作:

  • a 函数声明,由于变量提升,此时函数内的作用域变量 a = function() {}。
  • 然后,a = 2 覆盖函数内 a 的值,打印出函数内部的 a 的值为 2。

因为没走外面的全局变量 a ,所以内部函数无论置不置顶,或者使用 let , var 关键字声明变量完全没影响。

请允许我再举个小小的栗子:

    !(function b() {
        let a = 1; //Uncaught SyntaxError: Identifier 'a' has already been declared
        function a() {}
        console.log(a); //如果使用 var 声明变量,则打印出 1
    })();

可以看到,上述代码直接报了一个错误,这种错误是使用 let 关键字声明变量之后再重复声明同一个变量才会报的错误,通过这个结果可以得知,函数体内声明一个函数,相当于在置顶处使用 var 声明一个同名变量。

最后结语

实际上,这并不是规范的一部分,它是Web遗留兼容性语义的一部分,因此不要在块内声明函数,也不要依赖此代码以这种方式运行。



不管你是初级还是高级,只要你喜欢讨论技术,我都欢迎加QQ群讨论~
QQ群:615766495
Q群链接:前端FeNext