面试官(4):请你讲讲你对“提升”的理解

120 阅读4分钟

前言

相信经历了前面的学习,我们已经熟练的掌握了作用域,以及变量分配的原理。总的来说,就是声明在某区域的变量,都附属于这个作用域。

但作用域与当中的变量声明出现的位置有某种联系,这篇文章将会讨论这个细节问题。

运行顺序

以我们的写作习惯来说。我们一般认为,代码的执行就是一行一行依次往下运行过去的啊。事实却并不完全如此,还是存在特殊情况,导致我们的想法错误。

以下面的代码做一个例子:

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

让我们思考一下,这里的console.log()会输出什么呢?

许多开发者会认为是undefined,因为var a 声明在 a=2后面。按照代码顺序,我们自然会认为a被重新赋值了,所以会被赋予默认值undefined。但是,真正的输出结果是2

让我们再看一段代码:

    console.log(a);
    var a = 2

这又会输出什么呢?

有了上面的教训,我们可能会认为这也会输出2。有些人又有其他意见了:变量a在使用之前没有声明啊!因此会抛出ReferenceError错误才对。

糟糕的是两种意见都是错误的,这里会输出undefined。此刻我们好像陷入了一个经典哲学性问题中:到底是先有鸡(赋值),还是先有蛋(声明)?

编译器又出现

为了弄清这个问题,我们要先复习一下编译器的内容。[详情请看](大厂面试官(2):请你细说作用域的底层运算逻辑。 - 掘金)。引擎会在解释JavaScript代码之前进行编译。编译中的一部分工作就是找到所有的声明,并用合适作用域将其串联起来。

所以,包括函数和变量在内的所有声明都会在代码执行前首先被处理。

当我们看到var a = 2 ,第一反应会认为这是一个声明。但JavaScript会把他分为两个部分var aa =2,第一个在编译阶段执行,第二个在执行阶段执行。

上面的第一个代码会被认为是:

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

其中第一部分是编译,第二部分是执行

类似,第二个代码实际上是以这样的流程执行的

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

因此,变量和函数声明从代码中的位置被移动到最上方,这个过程就是提升

所以这个哲学问题也能解决:先有蛋(声明),再有鸡(赋值)。

注意:只有声明会被提升,而赋值和其他代码会留在原地。如果提升改变了代码执行顺序,会受到严重的破坏。

函数优先

函数声明和变量声明都会进行提升。但是函数会被首先提升,然后再是变量。

思考以下代码:

   foo();// 1
   var foo;
   function foo() {
       console.log( 1 );
   } 
   foo = function() {
       console.log( 2 );
   }

此时代码会输出1而不是2

引擎眼中的代码是:

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

此时尽管var foo 出现在function foo之前,但他是重复的声明(被忽略),因此函数声明会被提升到普通变量之前。

小结

我们习惯将var a = 2;看作一个声明,而实际上JavaScript引擎并不这么认为。它将var a 和a = 2当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。

这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。 可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的 最顶端,这个过程被称为提升。

声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。

要注意避免重复声明,特别是当普通的var声明和函数声明混合在一起的时候,否则会引 起很多危险的问题