前言:本文章不会讲晦涩难懂的AO与GO对象,只以最通俗的人话讲述和理解JS的预编译过程。
一、基础知识预备
在了解预编译之前,需要必备以下两个知识点:变量的声明 和函数的声明。
变量的声明:
<script>
/*变量的声明*/
var a = 111; //通过var关键字声明变量
b = 222; //省略var关键字声明变量
</script>
可以看到,变量的声明有两种方式,一种是带关键字var,一种是不带var关键字声明,不带var的变量也称为隐式全局,至于二者之间有什么区别,下面的预编译过程会讲到。
现在来储备第二个知识点:
函数的声明:
<script>
/*函数的声明*/
//第一种,通过关键字function来声明
function fn(){
console.log('虹口吴彦祖');
}
//第二种,赋值式声明,也叫做匿名函数
var fn1 = function(){
console.log('山东彭于晏');
}
</script>
第一种是我们经常见到的,也是标准函数声明的语法,没什么可讲的
第二个因为声明的函数跟标准函数语法声明的在结构上少了函数名字,因此叫匿名函数,将匿名函数赋值给一个变量,这就是函数的第二种声明—>赋值式声明。
二、预编译
主要区别在于带var的变量会被提升到当前作用域的最前面,
在不讲AO与GO对象一些晦涩难懂的概念的前提下,预编译过程变的非常简单直接,主要来做两件事情:
1.声明所有var变量(初始值为undefined)
2.解析定义式函数语句
现在先来看第一件事情:之前我们预备知识以及说过了,变量有两种声明方式,现在预编译只对带var关键字的变量进行声明且做一个赋值操作,意思是另一种不带var 的就不带它玩了,它就老老实实在他的原位置上待着就好。
其实第一步是分为两步来执行的:
第一小步:声明所有var变量:将所有的var变量提升到当前作用域的最前面
第二小步:将变量的值赋值成undefined
下面来看代码:
如果打印输出a,会输出什么?毫无疑问是undefined,why?不应该是111吗?小小的脑袋,大大的疑惑!
这个时候,返回头去看我们的变量声明知识以及预编译知识,会发现,虽然我们用var声明了一个a变量,但是在预编译阶段,他只是将a提升到了当前作用域(也就是script标签)的下面,但是作为a变量的值111并没有跟随过来,反而是系统,也就是js引擎自动分配了一个值给a,这个值既然是统一分配的,那系统肯定不知道该给你赋一个什么数据类型(js六大数据类型Number、String、Boolean、undefined、null、object这些不知道的面壁思过去)的值,那就都赋值一个undefined好了。所以,a的初始值其实是undefined,因此在31行log输出a的值会显示undefined
那么什么时候a可以拿到属于他自己的值111呢,不能一辈子都是undefined吧?这显然是不科学的,答案很明显了,在32行以后获取到的a值都将是111,也就是说在32行变量a做了一个值更新,从undefined更新成了111。
现在带var的解决完了,不带var的之前说过既然你不带var,系统是不会带你玩的,就老老实实呆在原位置上
36行的打印输出,很明显在预编译过程因为b没有带var,系统不带它玩儿,是没有经过变量提升这么一个环节的,因此是找不到有b这个变量的,那就报错呗,b is not defined
既然报错了,下面的38行也将不能执行,JS代码的运行规则就是遇到报错直接停止运行在报错位置,不再往后继续执行。
现在来看预编译过程做的第二件事情
其实有关变量的预编译理解之后,函数的预编译也是同理,可以类比出来的
上代码:
代码中我们写的fn函数是先使用再声明,对于有名函数,这么干一点问题也没有,看下结果
正确输出结果!
但是类比不带var的变量,对于匿名函数来说,就会有问题了
当我们先调用再声明匿名函数时,就会报错了,我们期待他输出‘山东彭于晏’
可是,直接报错——fn1 is not a function 说fn1并不是一个函数
事实上,fn1它就是一个函数,但是为什么系统或者说js引擎说他不是一个函数呢?其实就是预编译过程出了差错,匿名函数如同不带var的变量一样,JS引擎并不会去管他,他还是老老实实呆在原位置就好。
如果在函数声明之前就调用了,那么JS在预编译过程中并不能找到这个函数,当然会被判定为不是一个函数了。
下面改一下,让它不报错,还能输出我们想要的山东彭于晏:
只需要将fn1的调用,放到函数声明下面的任何位置,都将输出正确结果。
可见,顺序对程序执行的重要性
三、总结:
JS的预编译,如同你这一生遇到形形色色的人一般,可能对你来说,他们的出现换一个顺序,有可能就改变你的一生。