详解闭包

59 阅读3分钟

一、简单介绍

闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

通俗来说,闭包 = 内层函数 + 外层函数的变量

二、作用域

想要理解闭包,首先必须理解JS的作用域,作用域分为全局作用域局部作用域。ES6新增了块级作用域。

1. 全局作用域

全局作用域中声明的变量,在代码的任何地方都可以访问,其生命周期伴随着页面的生命周期。

const num = 10
function fn() {
    console.log(num)
}
// 此处可以打印出结果,结果为10
fn()

function getNum() {
    // 因为没有声明变量,所以num变成了window的属性
    num = 10
}
getNum()
// 可以获取到num的值,打印结果为10
console.log(num)

2. 局部作用域

2.1 函数作用域

在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。

function getNum() {
    const num = 10
}
// 无法访问
console.log(num)

2.2 块级作用域

{ } 包裹起来的代码称为代码块, 使用 let 或 const 声明的变量, 在 { } 会产生块级作用域。

// 1. 只有let、const会产生块级作用域,var声明不会产生块级作用域
{
    function getNum() {
	const num = 10
    }
    // 可以访问
    console.log(num)
}

// 2. 块级作用域的外部不能访问内部的变量
for (let i = 0; i < 5; i++) {
    // 可以访问
    console.log(i)
}
// 无法访问
console.log(i)

// 3. 不同代码块之间的变量无法相互访问
for (let i = 0; i < 3; i++) {
    console.log(i)
} // 0 1 2 
for (let i = 0; i < 5; i++) {
    console.log(i)
} // 0 1 2 3 4

那么我们怎样才能从外部读取局部作用域呢?这就涉及到了作用域链这个概念。

3. 作用域链

在很多情况下,我们需要得到函数内的局部变量,因为函数是可以嵌套函数的,每个函数都有一个局部作用域,这样也会形成作用域的嵌套。所以我们只需要在函数的内部,再定义一个函数。

function fn1() {
    // 局部作用域
    let num = 10
    function fn2() {
        // 局部作用域
        // num = 20
        console.log(num)
    }
    fn2()
}
fn1() // 10

因为 fn2 嵌套在 fn1 中,这时 fn1 内部的所有局部变量,对 fn2 都是可见的。反之,fn2 内部的局部变量,对 fn1 就是不可见的。

所以,作用域的查找查找规则是:子对象会往上层作用域中查找变量,直到全局作用域。它的本质就是底层变量的查找机制。

三、闭包

上述代码中的 fn2 函数和外层函数变量的集合就是闭包。

1. 闭包的简单写法

function outer() {
   let num = 10
   function fn() {
      console.log(num)
  }
  return fn
}
const fun = outer()
fun()

外层函数就相当于把这个整体包裹起来了,所以叫闭包。

2. 闭包的应用

  • 保护函数内的变量安全。以最开始的例子为例,函数 outer 中的 num 只有函数 fn才能访问,而无法通过其他途径访问到,因此保护了 num 的安全性。
  • 在内存中维持一个变量。

eg:for循环中的定时器

for(let i = 0; i < 3; i++){
    ;(function(i) {
        setTimeout(function() {
            console.log(i)
        },1000)
    })(i)
}

使用闭包将 i 长期存储在内存中。

image.png

闭包可能会引起的问题:内存泄漏。