JS 作用域碎碎念

最后更新于

JS 里存在两类,一种是 block {},另一种是 function body (){}(包括 method),这两种块对不同的变量声明会有不同的作用域效果,下面就来看看。

var

if (false) {
  var a = 1
}
console.log(a) // undefined

var 是一种历史悠久的变量,他不认识 block {},会直接提升到第一个 function body 区,即使是 Dead-code 也不例外。

let, const

let a = 1
if (true) {
  const a = 2
}
console.log(a) // 1

letconst 严格存在于当前 block 内,不会发生任何变量提升。

function

if (false) {
  function foo() {}
}
console.log(foo) // undefined

function 也是一种定义变量的方式,他和 var 一样悠久,提升规则和 var 几乎一样,但有两点不同:

const a = 1
if (false) {
  var a = 2 // SyntaxError: Identifier 'a' has already been declared
}
const foo = 1
if (true) {
  function foo() {}
}
console.log(foo) // 1
  1. var 会不择手段地提升,碰到 const, let 之类硬茬就会爆炸;而 function 则温和很多。
console.log(a, foo()) // undefined, 2
var a = 1
function foo() {
  return 2
}
  1. var 只送一个 undefined 不同,function 会连着定义一起送出去,我们可以 看起来 在定义之前就使用他。实际上,可以认为 function 连着内容一起被提升到了当前函数块的顶部执行。

另外,当整个 function 作为表达式而不是语句存在时,其名称只会被限定在函数体内可以访问:

$button.onclick = function hello() {
  console.log(hello.name) // 'hello'
}

console.log(hello) // ReferenceError: hello is not defined

class

const A = 1
if (true) {
  class A {}
  A = 2
}
console.log(A) // 1

class 创造的变量约等于 let,不过需要注意的是,在 class 的静态表达式中是可以直接访问到其类名的,而 let 需要先执行完 class,反而不能这么使用:

class A {
  static a = A.name // 'A'
  static b = this.name // can also use `this`
}
let A = class {
  static a = A.name // ReferenceError: A is not defined
  static b = this.name // 'A'
}

注意到最后一个例子,A.b 可以拿到 'A',这是因为匿名类会被自动赋予左边的变量名。当左边不是一个变量的时候该类的名字就是空了:

console.log(
  class {
    static b = this.name
  }.b,
) // ''