闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
这是MDN对于闭包的官方解释。
而我对于闭包的理解,简单来说,就是内部函数引用了外部变量。
let num = 0
function add(){
num++
console.log(num)
}
function outer() {
let num = 0;
function inner() {
num++;
console.log('num => ', num);
}
return inner;
}
const add = outer();
add(); // 输出: num => 1
add(); // 输出: num => 2
这里当outer()函数调用返回的值赋值给add变量后,add变量就指向了inner()函数,这时,outer()函数因调用所产生的作用域本应该随着返回而销毁,但是,由于inner()函数中引用了outer()函数中的变量num,而inner()函数又被add变量所引用,于是outer()函数产生的作用域不会被内存释放,后面每次调用add()函数就能实现num的累加。
闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。
为什么会有闭包
- 封装变量:闭包可以创建私有变量,将变量封装在函数内部,从而避免全局命名空间的污染。这种封装性可以帮助我们编写模块化的代码,提高代码的可维护性和可重用性。
- 保持状态:闭包可以在函数调用之间保持状态。通过在闭包中定义变量,并在闭包内部修改和访问这些变量,我们可以实现在函数调用之间共享和保持状态的能力。这对于一些需要记住先前状态的场景非常有用,比如计数器、缓存等。
- 实现私有方法和属性:闭包可以用于模拟私有方法和属性。通过在闭包中定义一些私有变量和方法,并返回一个包含这些私有成员的对象,我们可以实现类似于面向对象编程中的私有成员的效果。
- 实现函数工厂:闭包可以用于创建函数工厂,即动态生成函数。通过在闭包中定义一些参数或配置,并返回一个新的函数,我们可以根据不同的需求生成不同的函数。
注意事项
需要注意的是,闭包会引用其创建时的变量,这可能导致内存泄漏问题。如果闭包持有对大量对象的引用,而这些对象又不再需要时,它们将无法被垃圾回收。
内存泄漏(Memory Leak) 是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
在上面例子上做个小改动:
function outer() {
let dom = document.getElementById('table') //改为一个引用变量
function inner() {
console.log('style => ', dom.style);
}
return inner;
}
const add = outer();
这里dom由于add一直保留对inner()函数的引用,导致它将一直无法被内存回收,从而造成一个最简单的内存泄漏。一旦这样的闭包数量太多,就会造成非常严重的内存泄漏问题。而解决方案就是在不需要用到add变量时,将其赋值为null,取消对inner()函数的引用。