阅读 1263

从变量提升理解预编译,就是这么简单,就是玩!!

变量提升

作用域

预编译

在了解JavaScript的预编译之前,让我们先来了解了解跟预编译相辅相成的两个小玩意!!(变量提升和作用域),变量提升又和作用域有着密切的联系,对变量提升和作用域有了很好的认识后,再来看预编译,简直跟喝水似的!!!让我们先来了解了解变量提升吧!!

集中注意力看这里看这里!!!

变量提升(Hoisting)

我们先不谈变量提升,先来看看这个例子:(上例子!!)

    var a = 112;
    console.log(a);
复制代码

这不是so easy!!,闭着眼睛就知道了,console.log(a)输出112。(感谢评论区大佬指出...原本这里敲的是2...)

da888427d2f2659804a71372893d9b50.jpg

我们把上面的代码做一点小小的变化。

    a =112;
    console.log(a);
    var a;
复制代码

这里又会有人直接说了,这还不简单嘛,有手就行,这不就啥也输出不来嘛,直接报错呗,这还需要问。

先不做过多解释,先上运行截图:(在浏览器的控制台上运行这一段代码)

QQ截图20210423222925.jpg

小伙伴们都直接惊呆了!!怎会如此,活久见。

1b36cfe2be4fa4f0a2d6db42f5d167b2.jpg

大家不要着急,这里就涉及到了我们要谈的变量提升问题。

在JavaScript中,函数及变量的声明都将被提升到函数的最顶部。变量可以在使用后声明,也就是变量可以先使用再声明。 我们知到JS中声明变量的关键字有两个(var 和 let),那么它们之间有什么区别呢?? 上链接:JavaScript:var与let的区别 这里我们移步到另一篇文章了解了解var与let的区别。

上面提到函数声明也有提升,这里我们再上一个例子:

fun();
function fun(){  
    var a = 456;
    console.log(a);
}
复制代码

bc5913bc0558bcffce7cbebae176e9f0.jpeg

到这里,大家就开始谨慎了起来!!类比前面的例子,瞬间自信心爆棚!!!console.log(a)输出456;就这呀,我还以为啥呢。

你以为这就完了嘛?这只是你以为,我们再来改改上面的例子:(阴险!)

fun();
console.log(b);
var b = function fun(){  
    var a = 456;
    console.log(a);
}
订正: 这里评论区大佬指点的是,这一段代码如果换成这样:
console.log(b);
var b = function fun(){  
    var a = 456;
    console.log(a);
}
fun();

也是会报错的,因为函数表达式的函数名不能在外面使用。这一部分就是让我们了解函数声明和函数表达式的区别
复制代码

QQ截图20210425104043.jpg

大家看看这里又会得出什么结果?嘿嘿!!

先上运行截图!!

QQ截图20210423231204.jpg

果然有诈!!!

见了鬼,为什么会报错?函数声明不是会变量提升嘛?

这里的var b = function fun(){},是一个赋值操作(也叫函数表达式),将fun(){}这个函数赋值给b,fun()并没有变量提升,这就是为什么会报错的原因了。这里也就是我们要搞清楚函数声明和函数表达式的区别了。

到这里相信大家已经初步了解了变量提升

b96451f3c609165df17f3fe4458160e9.jpg

现在我们来了解了解作用域

作用域

作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期(函数作用域,块级作用域用完销毁),通俗的理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期

作用域分为:

  1. 全局作用域:直接编写在script标签中的JS的代码,都在全局作用域中,全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问到。
  2. 函数作用域:调用函数时,创建函数作用域,函数执行完毕以后,函数作用域销毁

全局作用域

先来聊聊全局作用域吧。

全局作用域那不就是定义在全局!! 小 case,那还真是说对了。

f406ca62c1752d274c589a9ec20f4c4b.jpeg

但是还是得来解释解释(奸笑)

  • 全局作用域在我们页面打开时创建,在页面关闭时销毁
  • 在全局作用域中有一个全局对象window,它代表的是一个浏览器窗口,它由浏览器直接创建,我们可以直接使用

第一点就不需要解释了,直接来解释第二点。还是老样子,上例子:

    <script>
        var a = 123;
        var b = 234;
        function fun(){
        }
    </script>
复制代码

像这种直接定义在script标签下的,经过变量提升,将这些声明全都放在了全局作用域的最前面。

var a = 123;
console.log(window.a);
复制代码

QQ截图20210423233203.jpg

这里我们可以通过window.a访问到a;

9c313b3369d5fc3a765d86e9cb6f9731.jpeg

函数作用域

了解了全局作用域后,我们来聊聊函数作用域!!

相同理解简直不要太完美,顾名思义,函数内的区域。

f0f1a0d20c4069d9fb0587e29f112676.jpeg

我们再来做点其他的理解:

  • 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的
  • 在函数作用域中可以访问全局作用域的变量,在全局作用域中无法访问到函数作用域的变量
  • 当在函数作用域中操作一个变量时,会先在自身作用域中寻找,如果有就直接使用如果没有则向上一级作用域中寻找,直到找到全局作用域中,如果全局作用域中依然没找到,则会报错。

相信大家这个贼好理解,咱就不做过多解释了,就不上例子了,还是来个例子叭,显的更专业一点(嘿嘿!!)

var a = 123
function fun(){
    console.log(a);   //123
}
fun();
复制代码

结果毫无疑问 123!!!!!!!!

再来上个例子(提示:此处有诈)

var a = 123;
function fun(){
    console.log(a);
    a = 456;
}
fun();
console.log(a);
复制代码

别太着急回答,好好想想(奸笑)三分钟思考

... ... ...

三分钟到了。。。。。

又上截图:

QQ截图20210423235704.jpg

疑惑头一连串!!!!

这里有什么问题呢??来想想,仔细看fun()函数中的a有问题,有极大问题。 没错就是这个a出轨了,因为在函数中没有a变量的声明,在函数中出现类似于a = 456,未定义直接赋值,这种的的属于定义在全局作用域中的。这就解释了fun()函数外面的console.log(a)为什么是输出456了,被覆盖了。(上面var与let的区别中提到了的)

上面两个小菜吃完了,现在上主菜!!!!

acee120e1e6db6581ea60e33253760a7.jpeg

预编译

预编译分为函数体内的预编译和全局作用域内的预编译

函数体内预编译四部曲:

  1. 创建一个AO对象(activation object)
  2. 找形参和变量声明,找到之后将形参和变量声明作为AO对象的属性名,值为undefined
  3. 将实参和形参统一(将实参赋值给形参)
  4. 在函数体里找函数声明,将函数名作为AO对象的属性名,值赋予函数体

有了上面两个小菜开胃,这个主菜就有意思多了。 老规矩,上花生(例子):

function fun(a) {
            console.log(a); //function(){}
            var a = 123;
            console.log(a);//123
            function a() {};
            console.log(a); //123
            var b = function() {}//函数表达式
            console.log(b); //function(){}
            function d() {}
            var d = a
            console.log(d); //123
        }
        fun(1)
复制代码

开始用四部曲分析一波: 1:创建一个

AO:{
               
            }
复制代码

2:找形参和变量声明:均赋值为undefined

a:undefined
b:undefined
d:undefined
复制代码

3:将实参和形参统一:(将1放到a中)

a:undefined---->1
b:undefined
d:undefined
复制代码

第三步结束。 关键步骤第四步:

a:undefined----> 1 ---->function a() {}
b:undefined
d:function d() {}
复制代码

四部走完,开始常规操作(洒洒水啦!!) 只看第四步: (考虑var关键字定义的变量会产生覆盖) a的一开始由实参传入的1被第二行var a = 123覆盖,a的值变为123

第一个console.log(a)输出function a() {} 毫无压力
由上往下执行赋值操作:
    第二个console.log(a)输出123;
    第三个console.log(a)还是输出123;
    console.log(b)输出function b(){}  //因为其是将一个函数赋值给b的
    console.log(d)输出123
复制代码

不多说,上运行截图:

QQ截图20210424102329.jpg

结果完美匹配,神奇。这里就涉及到了函数声明和函数表达式的区别(上面没弄懂的这里就会踩坑)

全局下预编译三部曲

  1. 创建一个GO对象(Global Object)
  2. 寻找变量声明,将变量声明作为GO对象的属性名,值赋予为undefined
  3. 在全局中找函数声明,将函数名作为GO对象的属性名,值赋予函数体。

整体上跟函数体内的预编译差不多。 还是一样的配方,还是一样的味道。先上例题:

    global = 100
    function fn() {
      console.log(global); // undefined
      global = 200;
      console.log(global); // 200
      var global = 300;
    }
    fn();
    var global;
复制代码

根据步骤往下走:

第一步创建一个GO对象:

GO:{
              
            }
复制代码

第二步寻找变量声明,值赋为undefined:

GO:{
                global:undefined
                fn:undefined
            }
复制代码

第二步结束,第三步冲:

GO:{
                global:undefined;
                fn:function(){},
            }
复制代码

这就是全局下的预编译,发现没,全局下还有一个函数,所以函数还需要来按照上面的四部曲分析一波: 照旧,咱就不过多赘述了,大家按照上面的四部曲来分析一波,咱就直接贴出来了

AO: {
      global: undefined---->200---->300
    }
复制代码

文章到此就完毕了,瞬间腰不疼腿不酸了,人也变得自信了!!!

8093a7868c6e5e2efa14902af5468e72.jpeg

文章分类
前端
文章标签