详解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>块之内提升。

猜你喜欢

转载自www.cnblogs.com/ldq678/p/9758757.html