研究js的块级做用域中的变量声明和函数声明

昨天晚上在沸点看到一个小哥发了个沸点,代码很简洁,可是但是弄晕了我,评论区也是很热闹,我没事就研究了下,本身理解了下,感受差很少能够解释通了。先来看那是什么样的代码吧javascript

就是这个几行代码,反正我看了是不太明白为何这样子,另外若是是强类型语言的程序员看了估计会说这个编译都不会经过吧,类型都不匹配怎么赋值啊,hahahtml

如下全部代码都会讨论关于块级做用域的知识,不懂得能够先恶补阮一峰老师的文章 ES6 的块级做用域java

另外我所提到的默认变量都指的是没有用var ,let,const关键词声明的变量程序员

本文代码测试均在谷歌浏览器测试,支持es6语法,其他浏览器执行结果可能会有所不一样,不表明全部浏览器的结果es6

块级做用域内的默认变量

咱们来研究下块级做用域内的默认变量面试

运行结果是index.html:14 Uncaught ReferenceError: b is not defined 对于我来讲有点震惊,b不是默认至关于var了吗?事实并不是如此,咱们根据事实能够得出结论,块级做用域内的变量声明不会提高到全局做用域的顶层,咱们查看window也发现window并无这个属性。再看下面图typescript

报错:index.html:16 Uncaught ReferenceError: b is not defined浏览器

运行成功,输出 50,经过断点调试我发现,一旦 b =50执行后window上就有b这个属性了

无报错,第四行和第六行都输出了50,这说明块内定义的默认变量和块外访问的都是一个,也就是说块内定义的默认变量只有等到定义默认变量的那行代码执行后才会往window上挂载属性,这个行为跟var声明变量有点区别,咱们来看下若是在块内用var来声明b是什么结果

输出是

能够看到在块内用var声明变量后行为符合咱们的通常认知,b被提高到全局,一开始是undefined,直到执行赋值代码后才有值,var仍是那个var,历来没变过,哈哈函数

小结:

  • 在块级做用域内部声明的默认变量(不适用let,var,const修饰),只有等到执行过你定义那个变量的那行代码后才能够访问,才给window赋值这个属性,在那行代码以前访问会报错
  • 块内的 默认变量依旧是全局变量
  • 在块内的默认变量没执行以前不能够访问这个变量

块级做用域内的函数声明

在块级做用域内部的函数声明非常诡异,直接看代码测试

console.log(a);
        {
           function a(){}
        }
     
复制代码

输出 undefined 还记得块内的默认变量吗?它若是这样子写会报错的,可是函数声明就不会,由于正如阮一峰老师那篇文章所说

在块内的函数声明会提高到全局做用域顶部,而且相似var了一个同名的变量,那的确很相似var,默认值是undefined, 接着看

console.log(`${window.a},${a}`);
        {
            console.log(`${window.a},${a}`);
           function a(){}
        }
        console.log(`${window.a},${a}`);
复制代码

输出是

细心观察会发现,第一个console符合预期,第二个console彷佛为啥window.a是undefiend,而a是有值呢? 再来回想下阮一峰老师的文章提升,函数声明会提高到块级做用域的顶部,那此时执行第二个console的时候在块内的顶部其实已经有了声明,因此此时a有值,而此时window.a没有值我猜想,由于window.a是提早在外面var a的那个a,而这个彷佛也跟块内的默认变量有点类似就是块外的这个a只有等块内定义a的那行代码执行了才会赋值, 这个赋值行为跟块内直接访问a是不一样的,直接访问a至关于访问块内提高到顶部的函数声明,执行的时候就有值了。

小结

  • 块内的函数声明会提高到块内的顶部,同时也会在全局做用域用var声明一个同名的变量,初始值为undefined
  • 这个块外的全局同名变量的赋值时机是执行完块内那行函数声明语句后才赋值
  • 块内的函数声明会提高到块内顶部,区别提高到块外,它并不会用var去声明一个同名的变量

块内同时有同名的默认变量和函数声明

到了最精彩的地方了

看代码1

console.log(`${window.a},${a}`);
        {
            a = 50;
            console.log(`${window.a},${a}`);
           function a(){}
          
        }
        console.log(`${window.a},${a}`);
复制代码

打印结果是

1.按照前两节的总结来,块内有函数声明,此时在全局var了一个同名的变量a,也等于window.a 2.第一行输出没毛病,var a嘛没赋值,默认是undefined 3.执行默认的变量 a =50,注意,此时执行后不要觉得会像第一小节说的那样会给window挂载属性a,不会的,由于此时在块级做用域内部已经由于有了下面的函数声明,此时块级做用域顶部有了function a(){}的声明,你此时执行a = 50只是至关于赋值操做,没有任何声明,此时给a赋值的时候会查找做用域链有没有声明a,恰好函数声明提高到顶部了一个a,因此就把块内的a赋值为50,因此第二行打印winddow.a仍然是undefined,而a属于块内,此时被赋值为50了 4.按照第二节的总结,块外的那个跟块内函数声明同名的变量只有在函数声明那段代码执行后才会赋值, 因此最后一行代码执行时window.a已经被赋值了而且诡异的是块外的那个变量的值彷佛跟块内函数声明的函数绑定着,当执行function(){}的时候会给外面的那个变量赋值,由于块内那个函数声明被a =50覆盖了,因此当执行完 function a(){}以后块外的那个变量就被赋值为50了,而非仍是function(){}

看代码2

console.log(`1 ${window.a},${a}`);
        {
           console.log(`2 ${window.a},${a}`);
           function a(){}
           a = 50;
           console.log(`3 ${window.a},${a}`);
        }
        console.log(`4 ${window.a},${a}`);
复制代码

打印:

符合你的预期吗? 我以为须要解释下第四个输出为何window.a不是50,为何没有被覆盖呢,按照上面所说的 a= 50的时候好像这个a跟外面的那个同名变量绑定着,其实这里你注意到function a(){}和 a= 50的顺序了吗? 是先写的function a(){},再写的a =50;我猜想只有执行function a(){}这段语句时才会影响外面的那个同名变量,其余时候不会影响,一旦执行事后,外面的那个变量的值就定死了,因此包括第三行,第四行输出的window.a已经不受a =50影响了,我又测试了下

console.log(`1 ${window.a},${a}`);
        {
    
           console.log(`2 ${window.a},${a}`);
           function a(){}
           a = 50;
           function a(){} // 再增长个
           console.log(`3 ${window.a},${a}`);
        }
        console.log(`4 ${window.a},${a}`);
复制代码

我在 a =50以后又写了个funaction a(){},正如我说,只有执行function a (){}的时候会触发改变外部的那个同名的全局变量,没执行一次就会触发改变外部的那个变量,而且对外部变量赋的值是跟内部同名的函数名绑定着。

小结

  • 块内的函数声明每次执行的时候都会给全局那个同名的变量赋值一次,而且,只有执行那个定义函数声明的代码才会触发赋值,你写的函数声明就至关于setter,每执行一次就给外部的那个同名的变量赋值一次
  • 若是块内同时有同名的函数声明和默认的变量声明,那给默认的变量赋值时其实至关于赋值给那个同名的函数,由于查找块内的做用域链时找到了,就不会往全局声明了

总结

js这门语言真的是很神奇的语言,几行代码都能让我琢磨半天,归根揭底仍是弱类型致使的坑,你一个number类型的变量怎么能够赋值给function类型呢?若是能在编译时直接报错,估计就没这么多面试神题了,由于这就是不懂类型瞎赋值,可是弱类型也不是一无可取,这也是js牛逼的地方,另外都快0202了赶忙用typescript吧,用了一直爽!不再写别人没法重构的代码了!

我也是初次研究这个,可能理解的有不到位,或错误的地方,若是有须要修改的地方请提出来,我不误人子弟

再次声明,全部代码均在谷歌浏览器上测试,其他版本和其余浏览器结果可能会有所不一样!

相关文章
相关标签/搜索