理解JavaScript的预编译

2019-08-06  本文已影响0人  秋夜已凉

不要因为真相很难理解就不告诉我真相是什么

也不知道最开始是在哪个老师还是哪本书或者是哪个同学那里听来的,或者说学来的说js是一门不需要编译的语言,由浏览器直接解释执行。嗯很好理解嘛,当时觉得也很对,写完一个js文件引入到html中直接扔浏览器里面跑就OK了,并没有像Java那样要做一个javac hello.java 得到一个hello.class的编译步骤。但是当我学到es6的时候就彻底懵逼了,es6中的的模块章节中通篇都在讲述import 和 expert这两个风骚到飞起的指令的各种花样解锁姿势,各种引用,其中很重要很基本的一个概念就是它跟node中require的区别?区别是什么呢,就是es6中的import是编译时加载的,而node中的require是运行时加载的。还有另一个说法就是,es6中的import是静态编译阶段执行的,所以在你import的代码中不可以写任何的一丝一毫的js逻辑判断或者运算的代码。比如阮老师告诉我们

      import { 'f' + 'oo' } from 'my_module';   

这样写会报错,原因是import 是静态执行的而 大括号中的加法代码是运行时的操作,所以报错了。
大神们对于自己理解的知识说起来总是一笔带过,从不关心屌丝们对于一个概念为什么是这样的可能都不懂。
自己就曾在这里钻过很长时间的牛角尖,偏执于大家信口拈来的 ‘编译时’ 这个概念到底是发生在什么时候? 它都做了什么工作?
后来在看了一些书籍以及度娘之后终于有所领悟,为防止自己日后再次遗忘,特别记录在这里。
其实对于js的编译期或者说预编译,我自己是一直都知道的,只是自己没有把这个 动作 跟j s的编译联系到一块儿而已。直接上例子🌰

剧透: 其实你想象js中的hosting(变量提升)就懂了

理解预编译首先要弄清楚两个概念: 函数声明变量赋值

  function xiaoyu() {}   // 函数声明  

这种形式的写法是函数声明,也即是声明一个函数,这种写法,脚本在执行之前会做预编译处理(这里很重要哦,要记得函数声明是会有一个高的优先级的先编译后执行)

再来看另一种写法:

  var xiaoyu = function() {}    // 变量赋值

这种写法就属于是变量的赋值了,函数在js中也是一种数据,匿名函数作为变量赋值给定义的变量。这种形式的写法,在编译阶段也会做处理,但是!只会给变量xiaoyu分配一个内存空间,不会初始化(好吧,初始化为undefined了)具体值的初始化是在程序执行阶段。
好了接下来就可以正式看例子了:

        🌰1:
      function xiaoyu() {
        alert('xiaoyu')
      }
      xiaoyu()
      function xiaoyu() {
        alert('xiaoyu2')
      }
      xiaoyu()

分析这段代码,首先判断两个都属于是函数声明,都会在预编译阶段处理,而函数名相同,后面声明的会覆盖前面的在执行阶段就只会看到后面的。所以将代码复制到控制台执行会看到两个xiaoyu2

      🌰2:
      var xiaoyu = function () {alert('xiaoyu')}
      xiaoyu()
      xiaoyu = function(){alert('xiaoyu2')}
      xiaoyu()

分析代码,首先判断两个都属于是变量赋值,而且两个变量名一样,所以在编译阶段只会分配一个内存空间存放变量xiaoyu的内容,当代码执行时候按照顺序执行和赋值,会先后弹出xiaoyu 和xiaoyu2

      var xiaoyu    // undefined
      xiaoyu = function () {alert('xiaoyu')}
      xiaoyu() //   'xiaoyu'
      xiaoyu = function(){alert('xiaoyu2')}
      xiaoyu() // 'xiaoyu2'
      明白了吧

      🌰3:
      function xiaoyu() {alert('xiaoyu')}
      xiaoyu()
      xiaoyu = function() {alert('xiaoyu2')}
      xiaoyu()

分析代码,首先第一个属于函数声明,后一种属于变量赋值。所以预编译处理完后代码应该是这样的:

      var   xiaoyu   // undefined
      function xiaoyu() {alert('xiaoyu')}
      xiaoyu()    //   'xiaoyu'
      xiaoyu = function() {alert('xiaoyu2')}
      xiaoyu()   // 'xiaoyu2'
      🌰4:
      window.alert(xiaoyu)
      function xiaoyu() {}
      window.alert(xiaoyu)
      var xiaoyu = 123

以上代码编译结束应该是这样的:

      var xiaoyu   // undefined
      function xiaoyu () {alert('xiaoyu')}
      window.alert(xiaoyu)   //  function xiaoyu() {}
      window.alert(xiaoyu)  // function xiaoyu() {}
      xiaoyu = 123

总结可以得出,函数声明和变量声明会在预编译阶段被’提升‘并且变量的提升是被最优先的提升的,也就是说如果一个函数声明一个和一个变量同名了比如例子三中的那么变量名会被优先提升到最高 var xiaoyu // 不赋值 为undefined 然后提升函数声明 function xiaoyu() {alert('xiaoyu')} // 此时因为函数声明在后所以它覆盖了xiaoyu变量给它复制为一个函数。当js引擎做完所有的这些提升的工作后js才会按照代码顺序来执行。

另外需要注意的是 js不是全文编译完成再执行,而是块编译,即一个script块中预编译然后执行,再按顺序预编译下一个script块再执行 但是此时上一个script快中的数据都是可用的了,而下一个块中的函数和变量则是不可用的。

OK 讲到这里,是不是有点懂得es6中大编译时执行是什么意思了,它就是在代码执行前做的一个预编译也可以叫做静态编译,好处是让你可以在执行任何代码前预初始化更多的模块结构,这样如果引用尚未赋值的export,能得到更好的错误信息。例如,一个let绑定会扔出异常——如果你在它被赋值之前就引用它的话——你可以得到清晰的错误信息。而一个动态模块对象上的属性如果还未赋值就被引用,得到的是undefined,最终错误可能发生在客户代码中,必须跟踪这个错误直到源头——这比异常要难调试太多了。

累了,先写到这里

上一篇 下一篇

猜你喜欢

热点阅读