闭包指的是JavaScript中的一种特殊函数,它引用了外层作用域中的变量。

函数是JS中的一等公民👑

理解闭包,要从先理解JS中的高级函数开始,在JS中,函数既可以作为另一个函数的参数来传递,又可以作为另一个函数的返回值来使用。将函数作为参数或返回值的函数叫做高阶函数

  • 将函数作为参数
// NOTE:封装函数,工具库里常用
// 封装加减运算
function calc(n1, n2, calcFn) {
  console.log(calcFn(n1, n2));
}
function add(n1, n2) {
  return n1 + n2;
}
function sub(n1, n2) {
  return n1 - n2;
}

var n1 = 20;
var n2 = 11;

calc(n1, n2, add);
calc(n1, n2, sub);

  • 将函数作为返回值
// NOTE:JS允许函数内部定义函数(嵌套定义),返回值为函数,就可以制造函数
function makeAdder(count) {
  return function add(num) {
    return count + num;
  }
}

// 构造函数
const add5 = makeAdder(5);
const add7 = makeAdder(7);
  • 一些JS自带的高阶函数
//数组方法 forEach map filter find findIndex reduce

闭包是什么

JavaScript中的函数,如果它访问了外层作用域中的自由变量,那么该函数和外层自由变量共同构成了闭包。
举个例子
在这里插入图片描述
广义上说,下面函数demo也是闭包,因为它也访问了外层作用域中的变量name,只不过外层作用域为全局作用阈。

var name = "xs";
function demo() {
	console.log(name);
}

闭包引起内存泄漏

为了解释内存泄漏,需要画出函数的执行过程(图看着复杂,其实很简单)
当代码执行到第15行,需要执行函数foo()

  • 首先创建一个AO(foo)对象,来存储函数中foo内部声明的变量的函数,
  • 然后创建一个函数执行上下文FEC(foo),将其压入调用栈中,执行代码,将返回值给fn
  • FEC(foo)出栈,等待被回收
    对于代码
    var fn = foo()执行完毕后,FEC(foo)出栈被回收,FEC(foo)中VO对于AO(foo)的引用也会被回收,若没有引用指向AO,AO也会被回收。但是由于在全局变量fn对应的值为函数对象bar的地址,函数bar在词法解析阶段就确定了父级作用域为AO(foo),所以仍然有引用指向AO(foo),所以它无法被回收,称为内存泄漏

在这里插入图片描述

解决内存泄漏方法

不再使用fn时,将其值设置为null fn = null,当垃圾回收器从根开始找所有有引用的对象,找不到bar函数对象和AO(foo),会将其当垃圾回收

可视化内存泄漏

浏览器执行下列代码是,闭包函数引用的外层变量为4MB,创造了100个闭包函数,就有对应400MB的内存增量。所以浏览器内存上涨了400MB左右。将引用销毁后,过一段时间,内存被垃圾回收机制回收释放。

function createFnArray() {
  var arr = new Array(1024 * 1024).fill(1);// 一个整数int占据4个字节 4MB
  return function () {
    console.log(arr.length);
  }
}

var arrayFns = [];

for(let i = 0; i<100; i++){
  arrayFns.push(createFnArray()) // 400MB内存
}

setTimeout(() => {
  console.log('销毁');
  arrayFns = null;
}, 2000);

浏览器的内存变化:
在这里插入图片描述

未被引用的外层变量会被销毁吗

对于闭包函数bar,只引用了外层作用域中的自由变量name,没有用到age,那么age会被销毁吗?
理论上:ECMAScript规范中说整个AO(foo)对象都不会被销毁,那么age肯定存在
实际上:V8引擎为了提升性能,将没有用得到的age销毁了(万一age很大呢,销毁就很能提性能)

function foo() {
  var name= "why";
  var age = 18; // age还会在AO对象中存在吗? 会存在,整个AO对象都不会被销毁(ECMA是这样规范的
  // 但是V8引擎为了性能,会把age删除

  return function bar(){
    debugger // 在浏览器中打断点使用
    console.log(name);
  }
}

在断点处,可以看到Closure中只有“why”
在这里插入图片描述