JavaScript闭包理解
1.概述
闭包被很多人认为是一个很难理解的概念。其实闭包很简单,就是一个能够访问父函数局部变量的函数,父函数在执行完后,内部的变量还存在内存上让闭包使用。
闭包如下代码函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 的变量,函数 B 就被称为闭包。
function A() { let a = 1 function B() { console.log(a) } return B }
你是否会疑惑,为什么函数 A 已经弹出调用栈了,为什么函数 B 还能引用到函数 A 中的变量。因为函数 A 中的变量这时候是存储在堆上的。现在的 JS 引擎可以通过逃逸分析辨别出哪些变量需要存储在堆上,哪些需要存储在栈上。
2.闭包面试问题
经典面试题,循环中使用闭包解决 var 定义函数的问题
for ( var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
运行结果如下所示,我们能够看到会打印出5个相同的i,值为6,我们可以分析出,首先因为 setTimeout 是个异步函数,所有会先把循环全部执行完毕,然后线程再去执行异步操作,这时候for循环已经将 i递加,并且值 就是 6 了,所以会输出一堆 6。
解决办法有三种,第一种使用闭包:
for (var i = 1; i <= 5; i++) { (function(j) { setTimeout(function timer() { console.log(j); }, j * 1000); })(i); }
如下所示结果,当我们使用闭包的方式,能够正常打印结果,其实在代码中相当于是使用for循环生成了5个function,并且按照i进行传参数给function,这个为啥叫做闭包,是因为setTimeout调用了立即执行函数的变量j:
第二种就是使用 setTimeout 的第三个参数
for ( var i=1; i<=5; i++) { setTimeout( function timer(j) { console.log( j ); }, i*1000, i); }
通过传递参数给setTimeout第三个参数i值,通过传递参数的形式将i值传给j,然后可以看到如下面结果
第三种就是使用 let 定义 i 了
for ( let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
因为对于 let 来说,他会创建一个块级作用域,相当于
{ // 形成块级作用域 let i = 0 { let ii = i setTimeout( function timer() { console.log( ii ); }, i*1000 ); } i++ { let ii = i } i++ { let ii = i } ... }
显示结果如下:
3.更有意思的闭包问题
下面是一个更有意思的示例 — makeAdder 函数:
function makeAdder(x) { return function(y) { return x + y; }; } var add5 = makeAdder(5); var add10 = makeAdder(10); console.log(add5(2)); // 7 console.log(add10(2)); // 12
在这个示例中,我们定义了 makeAdder(x) 函数,它接受一个参数 x ,并返回一个新的函数。返回的函数接受一个参数 y,并返回x+y的值。
从本质上讲,makeAdder 是一个函数工厂 — 他创建了将指定的值和它的参数相加求和的函数,当我们打印出add5和add10的时候,能够看到函数工厂生成的函数
在上面的示例中,我们使用函数工厂创建了两个新函数 — 一个将其参数和 5 求和,另一个和 10 求和。
add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。
4.实用的闭包
因此,通常你使用只有一个方法的对象的地方,都可以使用闭包。
在 Web 中,你想要这样做的情况特别常见。大部分我们所写的 JavaScript 代码都是基于事件的 — 定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常作为回调:为响应事件而执行的函数。
假如,我们想在页面上添加一些可以调整字号的按钮。一种方法是以像素为单位指定 body 元素的 font-size,然后通过相对的 em 单位设置页面中其它元素(例如header)的字号:
body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; }
我们的文本尺寸调整按钮可以修改 body 元素的 font-size 属性,由于我们使用相对单位,页面中的其它元素也会相应地调整。
以下是 JavaScript:
function makeSizer(size) { return function() { document.body.style.fontSize = size + 'px'; }; } var size12 = makeSizer(12); var size14 = makeSizer(14); var size16 = makeSizer(16);
size12,size14 和 size16 三个函数将分别把 body 文本调整为 12,14,16 像素。我们可以将它们分别添加到按钮的点击事件上。如下所示:
document.getElementById('size-12').onclick = size12; document.getElementById('size-14').onclick = size14; document.getElementById('size-16').onclick = size16; <a href="#" id="size-12">12</a> <a href="#" id="size-14">14</a> <a href="#" id="size-16">16</a>
5.闭包模拟私有方法
编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。
而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
var Counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })(); console.log(Counter.value()); /* logs 0 */ Counter.increment(); Counter.increment(); console.log(Counter.value()); /* logs 2 */ Counter.decrement(); console.log(Counter.value()); /* logs 1 */
在之前的示例中,每个闭包都有它自己的词法环境;而这次我们只创建了一个词法环境,为三个函数所共享:Counter.increment,Counter.decrement 和 Counter.value。
该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。
这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。
6.参考
面试题汇总
MDN闭包
深入理解javascript原型和闭包(15)——闭包
- 对javascript闭包的基本理解
- 关于本人对javascript闭包的理解
- JavaScript闭包的一些理解
- Javascript闭包简单理解
- Javascript闭包简单理解
- 深入理解JavaScript闭包概念
- 深入理解JavaScript闭包(closure)
- JavaScript闭包简单理解
- 深入理解JavaScript闭包
- javascript闭包理解之onload事件遍历获取数组元素
- 一些关于javascript闭包和this的理解
- 简易理解JavaScript闭包(Closure)
- 考一下你对Javascript闭包的理解
- 深入理解JavaScript闭包
- javascript闭包入门理解(一)
- 关于Javascript闭包的理解(三)
- 深入理解Javascript闭包 新手版
- 轻松理解JavaScript闭包
- 我对Javascript闭包的理解
- 深入理解Javascript闭包(closure)