闭包是怎么产生的?
在处于活动状态的执行上下文环境中创建了一个新的执行上下文,并且新的执行上下文中引用了除自身以外的变量的时候,就会产生一个闭包。
下面通过代码来理解一下上面这句话。
示例一
function foo() {var str = "你好🔥";function log() {console.log(str);}log(str)}foo();
以上代码就会产生一个闭包,闭包中会保存对 str 变量的引用,怎么证实呢?我们通过浏览器来断点看一下。
我们在代码第 4 行位置加一个断点,来看一下执行上下文中的内容。
我们可以看到 str 变量是保存在 Closure 中,Closure 是关闭/封闭的意思,它其实就是闭包,由此就可以证实,闭包确实被创建了。
接下来我们再来看另外两个也会产生闭包对例子。
示例二
function sum(arr) {return function () {return arr.reduce((a, b) => a + b)}}console.log(sum([1, 2, 3])()); // 6
这个例子和上面的例子基本一致,只不过函数被 return 出去了。
可以看到依然创建了闭包。
示例三
<ul><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li></ul><script>function start() {var liEls = document.getElementsByTagName("li");for (var i = 0; i < liEls.length; i++) {liEls[i].onclick = function () {console.log(i);}}}start();</script>
这可以说是一个很经典对示例,我们暂且先不考虑 i 的取值问题,先来看一下 i 是否也被保存在来闭包中,同样我们来断点调试一下。
当 li 元素被点击时,触发断点,我们看到 i 也是在 Closure 中,所以这段代码的执行也创建了闭包。
怎么避免闭包
有些时候我们不希望产生闭包,因为如果使用不当的话,闭包确实会产生一些性能问题,通过上面几个例子,我们进行一些改造,来看看怎么避免产生闭包。
示例一
function foo() {var str = "你好🔥";function log(str) { // 通过参数形式将 str 传入console.log(str);}log(str)}foo();
我们再来断点看一下
可以看到,这次没有产生闭包,因为我们把 str 以参数的形式传入到 log 执行上下文的 AO(活动对象)中了。
通过上面几个例子基本可以看出,只要函数发生嵌套,并且函数内引用了外部函数的变量,就会产生闭包。
示例二
function sum(arr) {return function () {return arr.reduce((a, b) => a + b)}}var data = [1, 2, 3];var tmp = sum(data);console.log(tmp()); // 6
示例一的方法在示例二中不太适用,因为我们的应用场景就是要先把数组传入 sum 函数中,等需要等时候在进行计算,所以我们就没有办法使用示例一中的方法了,既然产生闭包是必然的,那么我们就考虑如何避免闭包产生的内存泄漏问题呢?
怎么使用闭包
闭包其实没有平常说的那么可怕,只要平时使用得当,是完全没有问题的。而且闭包还会给我们带来很多便利,接下来说一下闭包的一些使用场景。
