无聊的谐音梗,“码”被提升

奇怪的变量提升现象

学生提了一个问题

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>

arguments对象和参数的互相引用

学生问了一个问题

function funcA(arr) {
  arr[0] = arr[2]
}
function funcB(a, b, c=3) {
  c = 10
  funcA(arguments)
  return a + b + c
}
console.log(funcB(1,1,1)) // 12

而如果funcB的c没有默认值

function funcA(arr) {
  arr[0] = arr[2]
}
function funcB(a, b, c) {
  c = 10
  funcA(arguments)
  return a + b + c
}
console.log(funcB(1,1,1)) // 21

以上两段代码的结果不同,这种现象是为什么?

这涉及到两个知识点:

1)这是因为严格模式和非严格模式的时候arguments对象的行为有所不同:

非严格模式:arguments对象和形参是互相引用

function func(a) {
  arguments[0] = 99;   // 更新了arguments[0] 同样更新了a
  console.log(a);
}
func(10); // 99

function func(a) {
  a = 99;              // 更新了a 同样更新了arguments[0]
  console.log(arguments[0]);
}
func(10); // 99

严格模式:arguments对象和形参互相无关

function func(a) {
  "use strict"
  arguments[0] = 99;   // 不会更新a
  console.log(a);
}
func(10); // 10

function func(a) {
  "use strict"
  a = 99;              // 不会更新arguments[0]
  console.log(arguments[0]);
}
func(10); // 10

非严格模式下的这个行为是很讨厌的,这也是为什么我们不喜欢潜规则,如果你不小心,它就会把东西弄得很复杂。

2)当函数中使用了参数的解构、默认值或…rest参数,则函数内部自动按严格模式解释

参考:MDN-Arguments对象

尝试运行下面这个例子,观察结果

<script>
  function func(a) {
    arguments[0] = 99 // 更新了arguments[0] 同样更新了a
    console.log(a)
  }
  func(10) // 10
</script>

<script>
  function func(a) {
    arguments[0] = 99 // 更新了arguments[0] 同样更新了a
    console.log(a)
  }
  func(10) // 99
</script>

<script>
  function func(a=1) {
    arguments[0] = 99 
    console.log(a)
  }
  func(10) // 10
</script>

<script>
  function func(a, ...b) {
    arguments[0] = 99 
    console.log(a)
  }
  func(10) // 10
</script>