Featured image of post Javascript 作用域与闭包

Javascript 作用域与闭包

JavaScript 有两种主要的作用域,即全局作用域和函数作用域。这些作用域决定了变量在代码中的可见性和生命周期。此外,ES6 引入了块级作用域,允许使用 letconst 声明的变量在声明它们的块内部可见,而不仅限于函数内部。块级作用域的变量只在块内部可见。

全局作用域

全局作用域是整个 JavaScript 程序的顶层作用域。在全局作用域中声明的变量可以在代码的任何地方被访问,包括函数内部。这意味着全局作用域的变量是全局变量,对整个程序可见。

1
2
3
4
5
6
7
8
var globalVar = 10 // 全局变量

function foo() {
  console.log(globalVar) // 在函数内部可以访问全局变量
}

foo()
console.log(globalVar) // 在函数外部也可以访问全局变量

在上述例子中,变量 globalVar 作为全局变量可以在函数 foo 中访问,也可以在函数的外部访问此全局变量。

函数作用域

函数作用域是在函数内部声明的作用域。在函数作用域中声明的变量只能在该函数内部访问,外部作用域无法访问。每次调用函数都会创建一个新的函数作用域。

1
2
3
4
5
6
7
function foo() {
  var localVar = 20 // 局部变量
  console.log(localVar)
}

foo()
console.log(localVar) // 无法访问局部变量,会抛出错误

在上述例子中,变量 localVar 作为定义在函数 foo 内的局部变量是无法在函数的外部去访问此变量的。

块级作用域

块级作用域是指在代码块内部声明的变量只在该块内部可见,而在块外部无法访问。块可以是由一对花括号 {} 包围的语句块,例如 if 语句、for 循环、函数内部,或任何其他代码块。

块级作用域的概念在 ES6 中引入,主要通过 letconst 关键字来声明变量。这与传统的 var 关键字声明的变量不同,后者具有函数级作用域或全局作用域。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if (true) {
  let blockVar = 30 // 块级作用域内的变量
  console.log(blockVar)
}

{
  let blockVar = 40 // 块级作用域内的变量
  console.log(blockVar)
}

console.log(blockVar) // 无法访问块级变量,会抛出错误

如果你用 var 关键字声明变量,那么该变量将具有函数级作用域或全局作用域,而不是块级作用域。

1
2
3
4
5
6
if (true) {
  var blockVar = 30 // 函数级作用域内的变量
  console.log(blockVar)
}

console.log(blockVar) // 30

即使你的变量声明在代码块内部,但如果使用 var 关键字声明,那么该变量将具有函数级作用域或全局作用域,而不是块级作用域。这就是 var变量提升

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function foo() {
  if (true) {
    var blockVar
  }

  console.log(blockVar) // undefined

  blockVar = 30

  console.log(blockVar) // 30
}

foo()

上述代码等同于以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function foo() {
  var blockVar

  if (true) {
    // var blockVar
  }

  console.log(blockVar) // undefined

  blockVar = 30

  console.log(blockVar) // 30
}

foo()

闭包

词法作用域

在理解闭包之前,我们需要先了解词法作用域。词法作用域(Lexical Scope),也称为静态作用域,是一种用于确定变量访问规则的作用域规则。在词法作用域中,一个变量的作用域是在代码编写时就确定的,而不是在运行时动态确定的。

在词法作用域中,变量的可见性是由变量在代码中的位置来决定的,通常是由变量的声明位置决定的。具体来说,一个变量在函数内部声明,那么它的作用域将限定在这个函数内部,而函数外部无法访问它。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var globalVar = 10

function foo() {
  console.log(globalVar)
}

function bar() {
  var localVar = 20
  foo()
}

bar() // 10

我们可以说变量 globalVar 的词法作用域是全局作用域,而变量 localVar 的词法作用域是函数作用域。

闭包的定义

闭包(Closure) 是指一个函数可以访问并操作其词法作用域之外的变量的能力。换句话说,闭包是指一个函数可以访问其词法作用域之外的变量,它可以访问并且“记住”其创建时所在的作用域,即使该函数在其词法作用域之外执行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function foo() {
  var localVar = 20

  function bar() {
    console.log(localVar)
  }

  return bar
}

foo()() // 20

举个例子,闭包相当于是一个背包,你可以将任意物品(变量)放入这个背包,当你关上背包后(离开当前词法作用域)只有等再次打开(调用闭包)才能拿到里面的物品。

函数 bar 定义在函数 foo 的内部,并且它的词法作用域与变量 localVar 重叠。当函数 foo 返回函数 bar 时,函数 bar 仍然可以访问变量 localVar,尽管函数 foo 已经执行完毕并且其词法作用域已经销毁。