奇怪的变量提升现象

学生提了一个问题

var a = 100
{
  a = 10
  function a() {}
  a = 20
  console.log(a) // 20 
}
console.log(a) // 10

最后打印20和10。这确实是一个奇怪的现象,因为按照以前对函数提升的理解,函数的声明部分会提升到生命周期的最前,赋值会提升到代码块的最前,这个例子显然不符合这个规则,那是怎么回事呢?

stackoverflow上有相同问题,看过答案后,现在我对这段代码的理解是,function的声明在代码块中的提升会产生两个提升,一个是到生命周期的最前端,一个是到代码块的最前端成为一个局部变量(生命周期是块,类似于let),所以形成内外两个生命周期的变量。而且function提升的位置永远比同var更靠前。

这样上面的代码就变成(a1表示外部生命周期的a,a2表示块作用域中的a)

var a1 <-- function 提升到外部的声明
a1 = 100
{
  function a2() {}
  a = 10 <-- 解释器解释到这时候a指向a1,所以a1=10
  a1 = a2 <-- function留在原地的部分要用a1赋值a2
  a = 20 <-- 解释器解释到这的时候a2已经提升了,所以它是a2=20
  console.log(a) // 20 <-- 这里生命周期较近的是a2,所以打印20
}
console.log(a) // 10 <-- 这里是a1,打印10

另外当function在块作用域中提升后,效果类似于let,所以还有这样的效果

{
  var x = 1
  function x() {} // 报错,重复声明
}

还有这样一个效果

{ //block 1
  function foo() { // declared in block 1
    return 1;
  }
  console.log("block 1: foo() === 1", foo() === 1);
  
  { // block 2
    function foo() { // declared in block 2
      return 2;
    }
    console.log("block 2: foo() === 2", foo() === 2);
  }
  
  console.log("block 1: foo() === 1", foo() === 1);
}
console.log("block 1: foo() === 1", foo() === 1);

/* 结果
block 1: foo() === 1 true
block 2: foo() === 2 true
block 1: foo() === 1 true
block 1: foo() === 1 true
*/

运行到block 2(7到12行位置的function foo),发现全局有一个变量已经叫foo,并且它处于block1处的局部变量foo1控制之下,所以会在此代码块里生成一个局部变量foo2(类似let声明的)而不覆盖全局的foo并且区分开和foo1的生命周期,所以造成这种现象。

如果注释掉2到5行,则全局变量foo也会被赋值为 block2处的foo。

总之,推荐用"use strict"可以减少这种奇怪的现象

<script>
"use strict"
var a = 100
{
  a = 10
  function a() {}
  a = 20
  console.log(a) // 20
}
console.log(a) // 100
</script>

发表回复