关于javascript:JavaScript深度剖析之变量函数提升从表面到本质

2次阅读

共计 4009 个字符,预计需要花费 11 分钟才能阅读完成。

JavaScript 深度分析之变量、函数晋升:从外表到实质

前言

系列首发于公众号『前端进阶圈』,若不想错过更多精彩内容,请“星标”一下,敬请关注公众号最新消息。

  • 想要彻底了解 晋升 这篇文章,除非你曾经了解了 作用域、词法作用域、动静作用域、编译器、引擎 之间的分割,否则倡议你先从之前的文章读起。
  • 在前几篇文章中提到的作用域中的变量申明呈现的地位有着某种奥妙的分割,而这个分割就是本篇文章所探讨的内容。

先有鸡还是先有蛋

  • 在咱们的直觉上 JavaScript 代码在执行时是一行一行执行的,其实并不完全正确,有一种状况会导致这个假如是谬误的。
  • 思考以下代码:

    a = 2;
    var a;
    console.log(a); //?这里会输入什么呢?
  • 可能会有人认为会输入 undefined,因为 var a 申明是在 a = 2; 赋值之后的,他们会自然而然地认为变量被从新赋值了,因为会被赋予默认值 undefined。但正确的输入后果为 2;
  • 再思考另外一段代码:

    console.log(a); //?这里会输入什么呢?var a = 2;
  • 鉴于上一个代码片段所体现出的某种非自上而下的行为特点,你可能会认为这段代码会输入 2。或者还有人可能认为,因为变量 a 在应用前没有当时被申明过,会抛出 ReferenceError 异样。然而,两种猜想都不会,正确的输入后果为 undefined
  • 那到底还是先有鸡还是先有蛋?到底是申明 (蛋) 在前,还是赋值 (鸡) 在前?,让咱们带着这个问题接着向下看。

编译器阶段

  • 依据后面分享的几篇文章咱们可得悉,引擎会在解释 JavaScript 代码之前会首先对其进行编译。而编译阶段中的一部分工作就是先找到所有的申明,并用适合的作用域将他们关联起来。因而,包含变量和函数在内的所有申明都会在任何代码被执行前首先被解决。
  • 当你看到 var a = 2; 时,你可能会认为这是一个申明。但 JavaScript 会将他们看成两个申明。var aa = 2;第一个定义申明是在编译阶段进行的 第二个赋值申明会被留在原地期待执行阶段
  • 第一段代码的解析过程:

    var a; // 被晋升后的申明
    a = 2;
    // var a;  // 留神, var a 会被晋升到顶部, 也就是下面提到的申明
    console.log(a); // 2
  • 第二段代码的解析过程:

    // var a;
    console.log(a); // undefined
    var a = 2;
  • 因而,这个过程就如同变量和函数申明从他们的代码中呈现的地位被 "挪动" 到了最下面,这个过程就叫做晋升。
  • 换句话说,先有蛋 (申明) 后有鸡(赋值)
  • 只有申明自身会被晋升,而赋值或其余运行逻辑会留在原地。如果晋升扭转了代码的执行程序,会造成十分重大的毁坏。
  • 思考以下代码:

    foo();
    
    function foo() {console.log(a);
    var a = 2;
    }
  • 依据下面两个示例代码,先不要看答案。你能够试着将下面这段代码的解析后的后果写进去,坚固实际一下。
    <details>
    <summary> 查看答案 </summary>
    <pre>

    function foo() {
        // var a; 晋升后的申明
        console.log(a); // undefined
        var a = 2;
    }
    
    foo(); // foo 函数的申明也被隐含地晋升了,因而第一行在调用 foo 可失常执行。

    </pre>
    </details>

  • 另外,须要留神的是,每个作用域都会进行晋升操作。这里的 foo(...) 函数本身也会在内容对 var a 进行晋升(并不是晋升到这个程序的最上方)。
  • 再思考以下代码:

    foo(); // 会输入 success 吗?var foo = function bar(){console.log('success');
    }
  • 其实并不会,晓得为什么吗?能够先本人想一下,再看上面的答案:
    <details>
    <summary> 查看答案 </summary>
    <pre>

    var foo;
    foo(); // TypeError: foo is not a function
    
    foo = function bar() {console.log("success");
    };
    
    /**
    你可能会纳闷为什么不是 ReferenceError?因为前面的 var foo = ... 对 foo 进行晋升,默认值为 undefined。因为并不会抛出 ReferenceError。为什么会抛出 TypeError?在后面几篇文章中咱们说过,对变量进行一些不合规的操作时则会抛出 undefined, 因而,这里对 undefined 进行函数调用,则抛出 TypeError。*/
    

    </pre>
    </details>

  • 因而,从下面的代码中得悉,函数申明会被晋升,但函数表达式并不会被晋升。
  • 再思考以下代码:

    foo();
    bar();
    
    var foo = function bar() {console.log("success");
    };
  • 本人能够先试着写出这段代码的解析后的后果,在查看答案:
    <details>
    <summary> 查看答案 </summary>
    <pre>

    var foo;
    foo(); // TypeError: foo is not a function
    bar(); // ReferenceError: bar is not defined
    
    foo = function {var bar = ...self...};

    </pre>
    </details>

函数优先

  • 函数申明和变量申明都会被晋升, 但呈现有多个 "反复" 申明的代码中是函数首先会被晋升,而后才是变量。
  • 思考以下代码:

    foo(); //?会输入什么呢?var foo;
    
    function foo() {console.log(1);
    }
    
    foo = function () {console.log(2);
    }
  • 本人能够先试着写出这段代码的解析后的后果,再查看答案:
    <details>
    <summary> 查看答案 </summary>
    <pre>

    function foo() {console.log(1);
    }
    
    foo(); // 1
    // var foo; 只管 var foo; 申明呈现在 function foo(...) 之前,但他还是反复申明,因而会被疏忽。因为函数申明会被晋升到一般变量之前。// 此处函数表达式并不会被晋升
    foo = function () {console.log(2);
    }

    </pre>
    </details>

  • 再思考以下代码:

    foo(); //?这里会输入什么呢?function foo() {console.log(1);
    }
    
    var foo = function () {console.log(2);
    }
    
    function foo() {console.log(3);
    }
  • 和之前一样,可先试试本人写出解析后的后果,再查看答案:
    <details>
    <summary> 查看答案 </summary>
    <pre>

    foo(); // 3
    // 只管反复的 var 申明会被疏忽掉,但呈现在前面的函数申明还是能够笼罩后面的函数申明的。function foo() {console.log(1);
    }
    
    var foo = function () {console.log(2);
    }
    
    // 会应用这个函数的后果
    function foo() {console.log(3);
    }

    </pre>
    </details>

  • 从下面代码能够看出,在同一个作用域内反复定义是很蹩脚的,常常会导致各种奇怪的问题。
  • 小测试

    • 思考以下代码:

      foo(); // 这里会调用那个函数?var a = true;
      if (a) {function foo() {console.log("a"); }
      }
      else {function foo() {console.log("b"); }
      }
  • 本人先写出解析后的后果后,再来看看本人的答案是否正确:
    <details>
    <summary> 查看答案 </summary>
    <pre>

    foo(); // TypeError: foo is not a function
    /**
        为什么会抛出 TypeError 而不是 ReferenceError?其实 foo(); 这段调用函数的代码会被解析成以下代码:var foo;
            foo();
            看到这里,你应该明确,为什么会抛出 TypeError 异样了吧。如果还是没了解,倡议你从头从新读起。*/
    var a = true;
    if (a) {function foo() {console.log("a"); }
    }
    else {function foo() {console.log("b"); }
    }

    </pre>
    </details>

小结

  1. 先有鸡(申明),后有蛋(赋值)。
  2. 记住如 var a = 2; 这段代码看起来是一个申明,但 JavaScript 引擎并不这么认为,它会将这段代码当做 var aa = 2; 两个独自的申明来解决,第一个是在编译阶段执行的工作,第二个是在执行阶段执行的工作。
  3. 反复定义的函数申明前面的会笼罩后面的。
  4. 函数申明会被晋升,但函数表达式并不会被晋升。
  5. 只有申明自身会被晋升,而包含函数表达式的赋值在内的赋值操作并不会被晋升。

特殊字符形容:

  1. 问题标注 Q:(question)
  2. 答案标注 R:(result)
  3. 注意事项规范:A:(attention matters)
  4. 详情形容标注:D:(detail info)
  5. 总结标注:S:(summary)
  6. 剖析标注:Ana:(analysis)
  7. 提醒标注:T:(tips)

    往期举荐:

  8. 前端面试实录 HTML 篇
  9. 前端面试实录 CSS 篇
  10. JS 如何判断一个元素是否在可视区域内?
  11. Vue2、3 生命周期及作用?
  12. 排序算法:QuickSort
  13. 箭头函数与一般函数的区别?
  14. 这是你了解的 CSS 选择器权重吗?
  15. JS 中 call, apply, bind 概念、用法、区别及实现?
  16. 罕用位运算办法?
  17. Vue 数据监听 Object.definedProperty()办法的实现
  18. 为什么 0.1+ 0.2 != 0.3,如何让其相等?
  19. 聊聊对 this 的了解?
  20. JavaScript 为什么要进行变量晋升,它导致了什么问题?

    最初:

  21. 欢送关注『前端进阶圈』公众号,一起摸索学习前端技术 ……
  22. 公众号回复 加群 或 扫码, 即可退出前端交流学习群,一起高兴摸鱼和学习 ……
  23. 公众号回复 加好友,即可添加为好友
正文完
 0