JS变量声明提升和函数声明提升

  • JS代码分为两个阶段:编译阶段和执行阶段;
  • 编译阶段:会找到所有的声明,并用合适的作用域将它们关联起来,这是词法作用域的核心内容,包括变量声明(var a)和函数声明(function a(){})在内的所有声明都会在代码被执行前的编译阶段首先被处理;过程类似于将变量声明和函数声明从他们在代码中出现的位置被移动到执行环境的顶部,这个过程就叫做提升,只有声明操作会被提升,赋值和逻辑操作会被留在原地等待执行;

一、JS是编译性语言
       JavaScript是脚本语言、编译性语言、解释性执行;和JAVA、C这种编译性语言的区别在于JS并不会像其他的编译语言一样进行提前编译,编译过程通常是在实际执行前进行的,而且也不会产生可移植的编译结果;

二、引擎、编译器、作用域

  • 引擎:负责整个JS程序的编译及执行过程;
  • 编译器:负责语法分析及代码生成等工作;
  • 作用域:收集并维护由所有声明的标识符(变量)组成的一系列查询,实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限,即作用域是一个变量的“管家”,用一个事先定义好的规则(词法作用域)管理变量的查询与访问;
  • 变量和函数在内的所有声明都会在当前块作用域内被首先处理,即类似于提升到最前面声明,但是赋值处理操作因为是在执行阶段,因此编译阶段他们原地待命等待执行;

三、变量声明提升

  // 变量声明可以看成两部分:声明操作(var a)+赋值操作(a=10)
  // 声明操作会在编译阶段进行,声明操作会被提升到执行环境的顶部,值是undefined(未初始化)
  // 赋值操作会留在原地等待执行操作
  var a = 2;

  function foo() {
    console.log(a); // undefined
    var a = 10;
    console.log(a); // 10
  }
  foo();
// 上面的代码相当于
var a = 2;

  function foo() {
    var a; 
    console.log(a); // undefined
    a = 10;
    console.log(a); // 10
  }
  foo()

四、函数声明提升

  • 定义函数有两种方法:函数声明和函数表达式;
  • 函数声明:函数声明提升会在编译阶段把声明和函数体整体都提前到执行环境顶部,所以可以在函数声明之前调用这个函数;
  • 函数表达式:其实是变量声明的一种,声明操作会被提升到执行环境顶部,并赋值undefined,赋值处理操作因为是在执行阶段,因此编译阶段他们原地待命等待执行;
// 函数声明
  foo(); // 100
  function foo(){
    console.log(100)
  }
// 函数表达式
  foo(); // TypeError: foo is not a function
  var foo = function(){
    console.log(100); 
  }
// 函数表达式的等价
var foo;
foo(); // TypeError: foo is not a function
foo = function(){
    console.log(100);
}

五、控制语句

  • JS中使用函数级作用域,不存在块级作用域,所有普通块中的声明都会被提升到顶部,所以控制语句对声明的控制就显得完全没有效果;
if (false) {
    var a = 10;
  }
  console.log(a); // undefined
// 相当于
var a;
  if (false) {
    a = 10;
  }
  console.log(a); // undefined
  • 奇怪的函数声明:函数声明发生在所有代码执行之前,所以尽管a函数的定义过程写在了if分支中,但是理论上,它是不会影响函数声明提升的,在新版本的浏览器中会出现此问题,旧版本的浏览器中会在控制台中打印出100,这也提醒了我们尽量不要在控制语句中进行声明,会造成很多无法预知的bug;
console.log(a); // undefined
  if(false){
    function a(){
      console.log(100);
    }
  }
  a(); // TypeError: a is not a function 理论上应该是100

六、函数声明规则

  • 函数优先:
    1)提升操作会优先进行函数的声明;
    2)函数会首先被提升然后才是变量,重复的变量声明会被忽略,只剩下赋值操作,多个函数声明可以进行覆盖;
  • 声明的顺序是这样的:
    1)找到所有的函数声明,初始化函数体,如有同名的函数则会进行覆盖;
    2)查找变量声明,初始化为undefined,如果已经存在同名的变量,就什么也不做直接略过;
  // 1
  foo(); //200

  function foo() {
    console.log(100);
  }

  function foo() {
    console.log(200);
  }
  
  // 2
  console.log(foo); //function foo(){...}

  function foo() {
    console.log(200);
  }
  var foo = 100;

七、猿辅导的面试题

var x = 1,
    y = z = 0;

  function add(n) {
    n = n + 1;
  }
  y = add(x);

  function add(n) {
  n = n + 3;
  }
  z = add(x);
  console.log(x, y, z); // 1 undefined undefined
var x = 1,
    y = z = 0;

  function add(n) {
    n = n + 1;
    return n;
  }
  y = add(x);

  function add(n) {
    n = n + 3;
    return n;
  }
  z = add(x);
  console.log(x, y, z); // 1 4 4

参考链接:https://www.cnblogs.com/Gary-Guoweihan/p/6251870.html

你可能感兴趣的:(JS变量声明提升和函数声明提升)