前言
本文是作者学习阶段对闭包的理解,主要介绍了闭包的基础知识点和对知识点的理解。
本文排版约定
Q — 问题
A — 回答
关于闭包
1.是什么?
《你不知道的JavaScript》里的定义为:
当函数可以记住并访问所在的词法作用域时,就产生了闭包。即使函数是在当前词法作用域之外执行。
2.优缺点?
优点
- 实现封装,属性私有化。 在自己的函数作用域内,实现内部成员变量,提供function接口。
- 保护作用域不被释放,成员变量被缓存起来具有记忆性。
- 模块开发,防止污染全局变量。
缺点
- 闭包会导致作用域链不释放,造成内存泄漏
3.怎么用?(以累加器举例)
引入变量的生命周期
code - 1:
var x = 0;
function fn1() {
return ++x;
}
console.log(fn1());
console.log(fn1());
//输出:
//1
//2
code - 2:
function fn2() {
var x = 0;
return ++x;
}
console.log(fn2());
console.log(fn2());
//输出:
//1
//1
Q1:为什么输出的结果不同?
A1:我们知道JS是运行在浏览器,浏览器引擎有垃圾回收器。
fn1()和fn2()在完成自己的"任务"之后,都被回收释放了。code -1 中的变量x为全局变量,缓存仍然存在,不会被回收;但code-2中的变量x定义在fn2()中,为局部变量,所以x一并被回收。
code-2代码中的fn2()被重复调用时,x一直在被重复定义并赋值为0,没有被缓存下来。
Q2:code-1实现了累加计数,为什么还要使用闭包这个概念,换言之code-1有什么缺陷?
A2:我们知道,在一个项目中需要使用到很多变量名,code-1将变量定义在全局范围内(这没有错),造成了全局变量的污染;并且变量x不常使用的话,闲置在内存中,浪费内存空间。
于是我们用上闭包
code - 2.1:
//这里用到了自执行函数(function(){})()
var add = (function () {
var x = 0;
function doAdd() {return x += 1;}
return doAdd;
})();
console.log(add());
console.log(add());
console.log(add());
//输出:
//1
//2
//3
此时,变量x就只作用于匿名函数所在的那个作用域,不会对全局变量造成污染。当code - 2.1执行完第一个console.log(add());垃圾回收器并不会对add进行回收释放,因为doAdd函数已经被保存到外部,作用域仍然存在。
总而言之累加器受匿名函数的作用域保护,只能通过 add 方法修改。
这也解释了闭包的缺点,如果写了太多的闭包,就会占用大量的内存,从而导致内存泄漏。闭包需要手动内存释放,让引用 add = null;即可
总结
-
通过案例学过 java 的朋友就不难想到 闭包和"类"非常的相似。
定义在函数内部的变量 -> 类中的成员变量
定义在函数中的方法 -> 类中的成员方法
好比对象,通过封装来让变量私有化,提供函数来访问成员变量。 -
最后,我理解得出闭包像是一种保护机制;闭包形成一个不销毁的栈环境,用来保护内部函数,成员属性不受外界的干扰。