面试中我写过的那些有关变量提升的题目

114 阅读5分钟

写在刚刚答完题目后,后知后觉发现自己做错了,但是我又改不了答案😭😭 故写此文章,希望自己把基础牢牢的印在大脑里。
本篇是从面试题目的角度来重温javascript的基础,日后遇到相关的题目也会更新在此文中。

先来一段理论分析

首先分析var a = 'global'这个代码段,在javascript引擎中会分两步执行,这也是本文的关键点

  1. 编译阶段,编译器会询问当前作用域是否有一个名称叫做a的变量存在作用域集合中,如果存在就忽略,继续编译;如果不存在,就在作用域的集合中声明这个变量,叫做a;
  2. 赋值/执行阶段,引擎会查找作用域中是否有这个变量a,如果有这个变量,就赋值为‘global’,如果这个作用域没有,那就要向上查找,在作用域链中查找是否有变量名为a的变量,查找到了就赋值,如果一直没有查找到,就抛出错误(严格模式下)。(关于作用域链这里不做展开说明)

看一道基础题

var a = 'global';
function foo(a) {
    console.log(a);
    var a = 'foo';
    console.log(a);
}
foo('params');

//'parmas','foo','foo';

以上,我们一点一点来分析

根据上面的理论我们来分析题目

编译阶段 :a先作为一个参数在函数内部声明成一个局部变量,然后再来一个 var a ,引用上面的解释,在作用域中查找到了这个变量,所以就忽略(这里相当于对a做了一个变量的重复声明)
执行阶段: 首先查找a这个变量,a作为参数被赋值成了'params',所以第一个 console 输出params;接着a被赋值成'foo',所以第二个输出'foo';

~~~然后题目再变形一下:~~~
var a = 'global';
function foo(a) {
    console.log(a);
    var a = 'foo';
    function a(){};
}
foo('params');

编译阶段 : foo 函数内部,a先为一个变量,然后遇到函数声明,就被声明为函数了。(重复声明的代码中,函数会首先被提升--函数优先)
执行阶段: console输出为 [Function: a]

~~~再变形一下~~~
var a = 'global';
function foo(a) {
    console.log(1,a);
    var a = 'foo';
    console.log(2,a);
    function a(){
        console.log('函数声明')
    };
    var a = 'foo2'
    console.log(3,a);
}
foo('params');

编译阶段 : 遇到函数声明,函数优先,后面对变量a的声明都被忽略;(⚠️这里容易对编译和执行赋值阶段弄混,js引擎是先编译,至于后面console会输出不同的答案,并不是后面又把a 声明为变量,而是对a这个函数进行赋值,所以才会输出不同的答案。)
执行阶段: 首先输出1 [Function a];然后对a赋值为foo,所以第二行输出 2 foo;下一步还是赋值,所以输出 3 foo2;

~~代码段1
var friendName = 'World';
 (function() {
   friendName = '试图改变全局函数'
   console.log('立即执行', friendName);
 })();
 console.log('最后一行', friendName);
 // 最后一行输出 最后一行 试图改变全局函数
 
 ~~代码段2
 var friendName = 'World';
 (function() {
   friendName = '试图改变全局函数'
   console.log('立即执行', friendName);  // 立即执行 试图改变全局函数
   var friendName = 'hello'
    console.log(friendName) // ‘hello’
 })();
 console.log('最后一行', friendName);
 // 最后一行输出: 最后一行 World

编译阶段 : 代码段1里,在立即执行函数里面就没有声明的函数;代码段2里,有变量声明;
执行阶段: 代码段1对变量friendName赋值,在函数作用域里面没有查找到,就去作用域链查找,最终查到就赋值;
因此在代码段1里 最后一行输出,对全局作用域里面这个变量进行了修改
在代码段2里,对变量friendName赋值,在函数作用域里就找到了这个变量,所以赋值给函数作用域里面的变量friendName

当函数声明遇上if代码块

 var friendName = 'World';
 (function() {
   if (typeof friendName === 'undefined') {
     var friendName = 'Jack';  // 这里变量提升了
     console.log('Goodbye ' + friendName);
   } else {
     console.log('Hello ' + friendName);
   }
   if (false) {
     function foo(){
       console.log('函数声明在if里面会被提升')
     }
   }
   console.log(typeof foo,foo);
   console.log('这里打印一个没有被声明的变量SS',typeof ss, ss);
 })();

最后输出 Goodbye Jack / undefined undefined / 最后一行报错
在if的代码块里面,变量声明和函数声明都会提升,但是都声明为undefined;
因此第一行输出Goodbye Jack
第二个if代码块里面,foo函数被提升了,但是被声明为undefined;所以第二行输出undefined;
第三行输出是做一个佐证,如果一个变量没有被声明是会报错的,证明了foo函数是提升了但是声明成undefined。(PS:关于函数声明和if的说明可以看MDN的解释

总结

在javascript中,编译和执行是分开的。当我们遇到一段代码块,也应该按照编译和执行两个步骤分开来分析,并且记住以下几点规则:

  1. es5中,在编译过程中,变量的声明是可以重复的,并且以函数声明为优先;对于函数里的形参,它和函数中声明的变量对于js引擎来说都是一样的,在编译过程中都把他们作为变量保存在当前作用域中;
  2. 在执行过程中(赋值、执行),会从当前作用域开始,沿着作用域链去查找;找到最近的变量,就可以赋值或者读取。

后序

本文参考

本文的理论部分参考自《你不知道的javascript(上卷)》

下一篇报备

第一次在掘金发文,如有描述不对的地方,烦请评论指出。下一篇我将整理一下函数中参数的不同以及参数在es5和es6之间的区别。