前端

直观理解

  • 把函数内部的变量(包括参数)在别的地方被引用
    • 如函数中的回调函数进行了引用
    • 如函数返回的另一个函数中进行了引用

视频教程:

(https://www.bilibili.com/video/BV1z7411v7T1?p=4&share_source=copy_web)

  1. 1-3p即可大致了解
  2. 后面都是细节

适用范围:

1. 作用:

  • 使一个函数具有状态
    • 每次调用这个函数,都是基于上一次的状态

2. 应用场景:

  1. 实现函数防抖debounce, 等待空闲算法 防抖函数 debounce
  2. 把一部分状态或者变量打包保存起来,不暴露在外部,一种私有化策略

前提知识:

  1. child::函数作用域链

实例说明:

之一

function A(){
	let 内部变量;
	function a(){
		console.log(内部变量);
	}
	return a;
}
let 闭包 = A();//因为这条语句,导致"内部变量"一直保存在内存中
闭包(); //获取"内部变量"
闭包 = null; //释放闭包, 释放内存

等同于:

function A(变量=默认值){
	function a(){
		console.log(变量);
	}
	return a;
}
let 闭包 = A();//因为这条语句,导致"内部变量"一直保存在内存中
闭包(); //获取"变量"的值
闭包 = null; //释放闭包, 释放内存

之一

for (var i = 0; i < 5; i++) {
    (function (a) {
        setTimeout(function () {
            console.log(a)
        }, 1000)
    })(i)
}
  • [base::块级作用域]
  • 每次循环,都会声明一个新的匿名函数,并且把i的值赋给函数的a变量
    • 即有地址中有5个不同的a变量
  • 而这个a变量被settimeout的回调函数所引用,所以a不会被清理释放
    • 直到被settimeout执行完,清理回调函数,被引用的a也随着被释放

之一

function a() {
    let b = 1
    return { a: b, log: () => { console.log(b++) } }
}
let obj = a()
obj.log()//1
obj.log()//2
console.log(obj)// { a: 1, log: [Function: log] }
  • 因为a函数的作用域中的变量被另一个函数所引用,所以该变量不会被清理
  • 而对象的a只是在声明的时候引用了b,并以此获取b变量的值,之后便没有引用了,所以a属性并没有构成闭包

梗概:

  1. 针对一个函数A
  2. 函数A返回一个函数a
  3. 在函数外部(全局作用域), 用一个变量引用调用函数A后返回的函数a
    1. “引用”即把函数a赋值给变量, 可以理解为用变量缓存了函数a
  4. 则这个保存了函数a的变量称为闭包
  5. 通过()调用这个变量, 函数a就具有了状态
  6. 状态保存在函数A中
  7. 通过变量=null, 把闭包释放掉, 清理内存
  8. 清理状态

大致原理:

  1. 通过全局作用域的变量保存了函数A返回的函数a, 所以函数a的作用域不会被释放
  2. 而函数a中又用到函数A的作用域, 所以函数A的作用域也不会被释放
  3. 当把闭包的引用覆盖掉之后, 没有被引用的函数a和函数A就会被释放掉