Javascript 的变量提升与预解析

一、什么是变量提升

  在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域。变量提升即将变量声明提升到它所在作用域的最开始的部分

二、怎么实现变量提升

  Js 运行前有一个预编译的过程,预编译完成后,在一步步执行。那么在预编译的过程中,会执行三个动作:

    1.分析参数,

    2.分析变量声明,

    3.分析函数声明。

  具体步骤如下:

    1.函数在运行的瞬间,生成一个活动对象(Active Object),简称 AO

    2.分析参数

      函数先接收形参,添加到 AO 的属性,并为这个值赋上 undefined,如:AO.age = undifened.

      接收实参,添加到 AO 的属性,覆盖之前的undefined 值。

    3.分析变量声明,如 var age; || var age = 23;

      如果在参数分析时,AO 并没有age 属性,则先为 AO 添加 age 属性为 undefine,即 AO.age = undefine.

      如果此时 AO 中已有age 属性,则不做任何操作。

    4.分析函数的声明

      函数的声明一般有3种形式:

        函数关键词声明:function age() {};

        函数字面量声明:var age = function () {};

        构造函数声明:var age = new Function();

      如果函数是函数关键词声明,即 function age () {}, 则把整个函数提至顶端,覆盖上一步中 AO.age 属性,即此刻 AO.age = function () {};

      如果函数是函数字面量声明,即 var age = function () {}, 则将 age 当成变量声明处理,即 var age。

      第三种形式暂不讨论。

  代码示例1

<script>
    function t1(age) {
        console.log(age);
        var age = 27;
        console.log(age);
        function age() {}
        console.log(age);
    }
    t1(3);
</script>

  预编译阶段的故事:

    1.创建 AO 对象

    2.分析参数

      接收形参 age,并赋值 undefine。AO.age = undefine

      接收实参 3,覆盖 上一步的 undefine。AO.age = 3

    3.分析变量声明 (var age = 27)

      由于第2步已有 age 属性,固这一步啥也不做。AO.age = 3.

    4.分析函数声明 (function age() {})

      由于是函数关键字声明,函数被提升到最顶端,替代第2步中的 age 属性,即AO.age = function () {}.

  于是,原本的代码预编译后等价于:

 1 <script>
 2     function t1(age) {
 3         var age=     function () {}
 4         console.log(age);   // function () {}
 5         age = 27;
 6         console.log(age);   // 27
 7
 8         console.log(age);   // 27
 9     }
10     t1(3);
11 </script>

  代码示例2

<script>
    function t1(age) {
        var age;
        console.log(age);
        age = 23;
        console.log(age);
        function age() {
            console.log(age);
        }
        age();
        console.log(age)
    }
    t1(22)
</script>

  预编译阶段的故事:

    1.创建 AO 对象

    2.分析参数

      接收形参 age,并赋值 undefine。AO.age = undefine

      接收实参 22,覆盖 上一步的 undefine。AO.age = 22

    3.分析变量声明 (var age;)

      由于第2步已有 age 属性,固这一步啥也不做。AO.age = 22.

    4.分析函数声明 (function age() {console.log(age)})

      由于是函数关键字声明,函数被提升到最顶端,替代第2步中的 age 属性,即AO.age = function () {console.log(age)}.

  于是,原本的代码预编译后等价于:

 1 <script>
 2     function t1(age) {
 3         var age=  function() {
 4             console.log(age);
 5         };
 6         console.log(age); // function () {console.log(age)}
 7         age = 23;
 8         console.log(age);   // 23
 9
10         age();  // 报错:age is not a function
11         console.log(age)
12     }
13     t1(22)
14 </script>

  执行阶段:执行到第10步时,由于age 在第7步被变量覆盖,所以再以函数的方式调时,发生了报错。

  示例代码3

<script>
    function t1(age) {
       console.log(1,age);
       var age = function() {
           console.log(2,age);
       };
       console.log(3,age);
       age();
      console.log(4,age);
    }
    t1(23)
</script>

  预编译阶段的故事:

    1.创建 AO 对象

    2.分析参数

      接收形参 age,并赋值 undefine。AO.age = undefine

      接收实参 23,覆盖 上一步的 undefine。AO.age = 23

    3.分析变量声明

      在这个例子中,没有变量的声明(var age = function () {console.log(age)},属于函数的字面量声明),固啥也不做,AO.age = 23。

    4.分析函数声明 (var age = function () {console.log(age)})

      由于是函数字面量声明,可以当做变量声明分析,即 var age = function age() {console.log(age)} 等价于 var age; age = function () {console.log(age)}。由于第2步中已有age属性,所以这一步什么都不做。AO.age = 23。

  于是,原本的代码预编译后等价于:

 1 <script>
 2     function t1(age) {
 3        age = 23;
 4        console.log(1,age);       // 1, 23
 5        age = function() {
 6            console.log(2,age);   // 2, function() {console.log(2, age)}
 7        };
 8        console.log(3,age);    // 3, function() {console.log(2, age)}
 9        age();
10       console.log(4,age);     // 4, function() {console.log(2, age)}
11     }
12     t1(23)
13 </script>

  执行阶段:第一个log打印时,输出的是预编译的结果,即 1,23, 然后在第5行,age 被覆盖成函数,但没有被调用,所以先走第8行的 log,即答应结果为 3,function (){...},然后在第9行,age 函数被调用,运行第6行函数内的log,所以打印出来的结果为 2, function (){...},最后执行第10行的 log,由于age 没有被修改,所以打印的仍为函数,结果为 4,function (){...}。因此,4个log 的打印顺序为:1,3,2,4

  至此,JS 预编译的过程我们差不多走通了。下面我们来总结变量提升的规律。

三、变量提升的规律  

  1:所有的声明都会提升到作用域的最顶上去。

  2:同一个变量只会声明一次,其他的会被忽略掉。

  3:函数声明的优先级高于变量声明的优先级,并且函数声明和函数定义的部分一起被提升。

四、最佳实践

  无论变量还是函数,都必须先声明后使用。以此来规范我们的代码,增强可读性和可维护性。

PS: 另有一道 JS 基础的综合训练题,感兴趣的朋友可以研读研读,做做练习。一道常被人轻视的前端JS面试题

原文地址:https://www.cnblogs.com/dyqblog/p/9926450.html

时间: 2024-11-05 13:46:16

Javascript 的变量提升与预解析的相关文章

关于Javascript的“变量提升”

先来看一段代码: var a = 1;   function b() {       a = 10;       return;       function a() {}   }   b();   alert(a); // the result is : 1 如果你觉得结果是"1"有点出乎意外,那么你应该往下看: 我们知道一个function里面是一个封闭的作用域,在其中用var或者function xxx的形式声明的变量或者函数,在封闭作用域中是不会受外部影响的.如果functio

11-变量声明提升和预解析

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <script> //预解析:js的解析器在页面加载的时候,首先检查页面上的语法错误.把变量声明提升起来. //变量值提升变量名,不提升变量值.而用function直接定义的

函数和变量中的预解析

1.js引擎运行 js 分为两步,预解析   代码执行 (1)预解析 js 引擎会把 js 里面的所有的 var 还有 function 提升到当前作用域的最前面 (2)代码执行 按照代码书写的顺序 从上往下执行 2.预解析分为 变量预解析(变量提升) 和 函数预解析 (函数提升) (1)变量提升 就是把所有的变量声明提升到当前的作用域最前面   不提升赋值操作 (2)函数提升  就是把所有的函数声明提升到当前作用域的最前面  不调用函数 案例1:(变量提升) console.log(num);

javascript Hoisting变量提升

1. 看人家举的两个例子,我认为这里的判断是否定义: !var 其实就是 指是否在函数function里面定义了.只有在funciton里面定义了了,js才hoist到最上面去找这个变量的值,否则就按照你自己在函数里定义的规则来了. [转载请注明来自: http://blog.csdn.NET/sunxing007] 下面的程序是什么结果? [javascript] view plain copy var foo = 1; function bar() { if (!foo) { var foo

JavaScript中变量提升------Hoisting

本文转自 damonlan的文章 http://www.cnblogs.com/damonlan/archive/2012/07/01/2553425.html 前言 因为我在写这文章的时候,百度里找资料,找到了园友的一篇文章,写的很好,可是我写了又不想放弃,所以就在里面拿了很多东西过来!~~ [翻译]JavaScript Scoping and Hoisting 希望得到大家谅解. 因为这个问题很是经典,而且容易出错,所以在介绍一次.哈哈.莫怪哦. 一.案发现场 我们先看一段很简单的代码: v

Javascript中变量提升的问题

一.函数声明变量提升 函数声明具有变量提升的问题,所以在函数被声明之前就可以访问. //else中的语句相当于将if中的function重写,因此无论flag为何值,返回的方法始终为重写后的方法. //将方法赋值给一个变量,方法就不会被重写,因此才能得到正确的结果. function functions(flag) { if (flag) { function getValue() { return 'a'; } } else { function getValue() { return 'b'

javascript 的变量提升特性

JavaScript 会提升变量声明.这意味着 var 表达式和 function 声明都将会被提升到当前作用域的顶部. bar(); var bar = function() {}; var someValue = 42; test(); function test(data) { if (false) { goo = 1; } else { var goo = 2; } for(var i = 0; i < 100; i++) { var e = data[i]; } } 上面代码在运行之前

javascript的预解析与变量提升

JavaScript是解释型语言是毋庸置疑的,但它是不是仅在运行时自上往下一句一句地解析的呢? 事实上或某种现象证明并不是这样的,通过<JavaScript权威指南>及网上相关资料了解到,JavaScript有“预解析”行为.理解这一特性是很重要的,不然在实际开发中你可能会遇到很多无从解析的问题,甚至导致程序bug的存在.为了解析这一现象,也作为自己的一次学习总结,本文逐步引导你来认识JavaScript“预解析”,如果我的见解有误,还望指正. (1)如果JavaScript仅是运行时自上往下

JavaScript的变量预解析特性

JavaScript是解释型语言是毋庸置疑的,但它是不是仅在运行时自上往下一句一句地解析的呢?事实上或某种现象证明并不是这样的,通过<JavaScript权威指南>及网上相关资料了解到,JavaScript有“预解析”行为.理解这一特性是很重要的,不然在实际开发中你可能会遇到很多无从解析的问题,甚至导致程序bug的存在.为了解析这一现象,也作为自己的一次学习总结,本文逐步引导你来认识JavaScript“预解析”,如果我的见解有误,还望指正.池州市贲生工艺品 我们先来看一个例子: var la