详解js运行机制(1)-- 预解析
一、问题的提出
我们都知道,js是一个解释型的语言,js代码在运行时,是按照js在文档中出现的先后次序,依次逐条语句执行的。那么问题来了。我们看下面这个小例子
<script type="text/javascript"> f1(); function f1(){ console.log('执行了函数f1'); } </script>
这个程序能正确执行吗?
如果按上面的理论,这个f1函数的调用出现在了声明的前面,显然当执行调用的语句时,js还没看到f1声明的部分,应该报错才对。但事实恰恰相反,这个程序是能正常执行的。这就牵涉到js程序执行的一个机制,叫做预解析。
二、预解析定义
其实js代码在执行过程分为两步,第一步叫预解析,第二步才是真正执行。
所谓预解析,就是:在当前作用域中,JavaScript代码执行之前,浏览器首先会默认的把所有带var和function声明的变量进行提前的声明或者定义。
对这句话的解析:
1.何谓当前作用域,就是你声明的变量和函数其所在的作用域,在上例中,f1函数是声明在全局作用域下的,所以声明f1的当前作用域就是全局作用域。
2.预解析不仅提升函数声明
3.预解析还会提升变量声明,也谓之变量提升
三、函数提升
js执行环境会先扫描当前作用域中所有var声明的变量和function声明的函数,然后把他们提升到当前作用域的顶端。
上例在运行时,f1的声明虽然是在调用语句之后,但其实js执行环境会对原始的代码做个预解析,解析过后的结果就变成下面这个样子
<script type="text/javascript"> function f1(){ console.log('执行了函数f1'); } f1(); </script>
差别很明显,一目了然,不解释。这样的代码当然能正常执行。
同理,形如
<script type="text/javascript"> f1(); f2(); function f1(){ console.log('执行了函数f1'); } function f2(){ console.log('执行了函数f2'); } </script>
这里声明的2个函数f1和f2都会被提升,提升过后的结果是这样的:
<script type="text/javascript"> function f1(){ console.log('执行了函数f1'); } function f2(){ console.log('执行了函数f2'); } f1(); f2(); </script>
四、变量提升
先看这个例子
<script type="text/javascript">
console.log(num);
</script>
这个代码执行时会报错:,因为num没有声明。
如果改成下面这个样子:
<script type="text/javascript"> console.log(num); var num = 10; </script>
结果是输出:undefined。意外吗?
原因是js执行环境对var num = 10;这条声明变量的语句做了提升,它等价于
<script type="text/javascript"> var num; console.log(num); num = 10; </script>
这就不难理解为啥输出的是undefined了。
再看一个经典的面试题:
<script type="text/javascript"> var num = 10; function f1(){ console.log(num); var num = 20; } f1(); </script>
这个例子输入的不是10,也不是20,正确答案是:undefined。
原因:此题代码等价与
<script type="text/javascript"> var num = 10; function f1(){ var num; console.log(num); num = 20; } f1(); </script>
在全局作用域下声明了一个变量num,在函数f1的作用域内声明了局部变量num,2个变量同名,那么在函数内部,起作用的是局部变量num,此时,后写的声明变量的语句var num = 20;被提升,当然它不会被提升到全局作用域,它只会被提升到声明这个局部变量的当前作用域的顶端,也就变成了等价代码的样子,所以输出就是undefined。
结论:
1.函数提升是整体提升
2.变量提升是只提升声明,不提升赋值
五、需要注意的地方
(1)函数表达式不会被提升
下面的例子执行时会报错
<script type="text/javascript"> f1(); var f1 = function(){ console.log('函数表达式'); } </script>
错误如下:。错误提示是f1不是一个函数。
为什么这里的f1没有被提升呢?因为f1是个函数表达式。
如果深究,f1也被提升了,但是它没有被当成函数提升,而是当成变量了。因为这个时候f1是通过var声明的变量,只不过它指向了一个函数对象。所以它提升过后的结果等价于如下带代码
<script type="text/javascript"> var f1; f1(); f1 = function(){ console.log('函数表达式'); } </script>
这里f1被当成变量提升的,而f1又没有赋初值,所以f1在调用的时候还是undefined,那当然会报错了。
(2)预解析不会跨越<script>代码块。函数和变量的提升,只在自己所在的<script>块之内提升。