对闭包的理解
什么是闭包
闭包这一词在之前学习 JavaScript 的时候就已经听说过了,但一直没去接触,也不知道是什么意思,后来才发现,在平常写代码的时候会不知不觉使用到闭包,像前几天总结节流与防抖的代码实现过程中,二者的函数封装当中就用到了闭包,在网上查了不少资料,再加上自己的理解,可以这么概括闭包:在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。
什么是词法作用域
既然闭包是基于词法作用域实现的,那么什么又是词法作用域?词法作用域是指在代码中声明变量的位置决定了这些变量在哪些地方可以被访问的规则。 在 js 当中函数的作用域是由函数声明的位置所决定的,也就是说函数可以访问其所嵌套的函数的变量。结合具体的代码可以更好地理解:
function outerFn() {
let name = 'PandaGuo';
function innerFn() {
console.log(name);
}
return innerFn;
}
const getName = outerFn();
getName(); // PandaGuo
在上面的代码中,innerFn 函数可以访问在其外部声明的 name 变量,因为在 innerFn 函数中访问 name 时,JavaScript 引擎会首先查找其所在的作用域,如果未找到,则继续向上查找到 outerFn 函数的作用域,然后在该作用域中找到了 name 变量并获取其值。这个过程就是基于词法作用域查找变量的过程。
闭包实现代码
大致理解完什么是词法作用域,再结合闭包的相关概念,下面给出闭包的具体实现代码:
function createCounter() {
let counter = 0;
const myFunction = function () {
counter = counter + 1;
return counter;
}
return myFunction;
}
const increment = createCounter();
const c1 = increment();
const c2 = increment();
const c3 = increment();
console.log('example increment', c1, c2, c3); // example increment 1 2 3
根据之前所说的:" 即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中 ",所以每一次外部函数执行结束后,myFunction 函数 当中的 counter 变量仍然保留在内存当中不会被销毁,所以最终执行完会打印出 " 1 2 3 "。
闭包的使用场景
- 模块封装:通过闭包封装模块,避免命名冲突,提高代码的可维护性。
- 延迟执行:通过返回一个封装了函数和环境的闭包,实现函数在之后的任何时间使用。
- 数据缓存:将一些计算结果缓存下来,避免重复计算,提高程序的效率。
- 柯里化函数:通过闭包的方式,将函数的参数分部传递,降低代码的重复性,提高代码的可读性。
闭包的优缺点
优点:
- 变量常驻内存,对于实现某些业务很有帮助,比如计数器之类的。
- 架起了一座桥梁,让函数外部访问函数内部变量成为可能。
- 私有化,一定程序上解决命名冲突问题,可以实现私有变量。
缺点:可能导致内存泄露
参考资料:
解读闭包,这次从ECMAScript词法环境,执行上下文说起 - 掘金 (juejin.cn)