JS面试题:变量提升和函数提升

733 阅读4分钟

1. 什么是提升

简单点来说,就是JS在执行代码前会进行预编译,预编译期间会将变量声明与函数声明提升至其对应作用域的最顶端

    console.log(a) // undefined
    var a = 1;

    console.log(func) // func() {console.log(123)}
    function func() {
      console.log(123)
    }

以上实例中,用var声明的变量提升到了当前作用域的最顶端,所以这边输出会是undefined;函数声明也会提升到最顶端,所以这边可以输出函数相关方法定义

2. 面试真题

题目1:var定义变量提升

console.log(a)
var a = 1
// 输出结果:undefined

以上实例实际上是a变量提升了,实际预编译成如下代码:

var a;
console.log(a)
a = 1

所以这边a输出会是undefined

题目2:var和setTimeout混用题型

   for (var i = 0; i <= 3; i++) {
      setTimeout(function () { //同步注册回调函数到异步的宏任务队列
        console.log(i) //执行此代码时,for循环已经执行完毕。
      }, 0)
    }
    // 输出结果:4 4 4 4

讲解: 这里需要了解JS的运行机制,JS执行顺序是先同步后异步;所以这里setTimeout是属于异步任务,先执行完同步任务,然后才会执行setTimeout,当执行到setTimeout函数内部的时候,for循环已经执行完毕了,所以此时输出的会是4个4,当执行完i=3之后,会去执行setTimeout,但是此时for循环已经执行完了,所以这边后面传入setTimeout中的实际上是4

题目3:let和setTimeout混用题型

    for (let i = 0; i <= 3; i++) {
      setTimeout(function () {
        console.log(i)
      }, 0)
    }
    // 输出结果:0 1 2 3

讲解: 把题目2中的var改成let就能输出我们希望正常的值了,var会出现那种情况是因为变量会被提升,并且var不受当前作用域的约束;而let就不一样,let声明的变量在for循环体作用域中使用的时候,变量会被固定,不会被外界干扰。

题目4:同步和异步题型

    for (var i = 0; i <= 3; i++) {
      console.log(i) // 0 1 2 3
      setTimeout(function () {
        console.log(i) // 4 4 4 4
      }, 0)
    }

讲解: 这里第一个会输出0 1 2 3是因为是同步操作,所以会依次输出对应的i值;而setTimeout中会等上面同步操作执行完之后,才会去执行,所以这边输出的就是4个4,详细解析请看上面题目2解析

题目5:函数和变量提升优先级

    function fun() {
      var a = 99;
      fun1();
      console.log(a);
      function fun1() {
        console.log(a);
        var a = 10;
        console.log(a);
      }
    }
    fun()
    // 输出结果:undefined 10 99

讲解: 当局部变量有定义参数a,则遵循就近原则,现在执行fun1函数,此时fun1函数中,变量a会被提升到fun1函数顶部,所以此时输出fun1中实际预编译如下:

function fun1() {
    var a;
    console.log(a)
    a = 10
    console.log(a)
}

所以这边先输出undefined后面输出10;当fun1方法执行完毕,则继续往下执行,此时程序下一步输出的a就是属于fun中的变量a,故而输出99

题目6: 函数提升优先级比var高

    var a = 1;
    function func() {
      console.log(a)
      var a = 2
      function a() {
        console.log(a)
      }
    }
    func()
    // 输出结果:function a() {console.log(a)}

讲解: 当var变量声明和function声明同名时,函数声明优先,所以这边输出的是个函数方法

题目7:函数提升和变量提升

    var a = 1;
    function func() {
      console.log(a)
      var a = 2
      function a() {
        console.log(a)
      }
      console.log(a)
    }
    func()
    // 输出结果:function a() {console.log(a)} 2

讲解: 这里因为函数声明比变量声明优先级更高,所以预编译的时候是先声明函数的,所以第一个输出的是一个函数方法,第二个输出一个赋值变量;以上实际预编译为:

var a = 1;
function func() {
    function a() {
      console.log(a)
    }
    console.log(a)
    a = 2;
    console.log(a)
}

这里会把函数声明提取到作用域顶部,所以此时第一个输出的函数方法,第二个输出是变量赋值

3. 解题思路总结

  1. 外部和内部都定义var变量,则就近原则提升
  2. 如果遇到函数声明和var声明同名,则函数声明优先级更高
  3. 遇到setTimemout异步方法和var混用,先执行同步的,后执行异步(setTimeout)