建议参考博客:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
闭包是函数和声明该函数的词法环境的组合。
这个环境包含了这个闭包创建时所能访问的所有局部变量。
先看下使用场景,就能懂闭包到底是什么,闭包能用来干嘛。
1.在函数外使用函数内的变量
function init(){
var name = "hello world";//name是一个被init创建的局部变量
function sayName(){//sayName是一个内部函数,闭包
alert(name);//使用了父级函数声明的变量name
}
sayName();
}
init();//"hello world"
sayName的alert语句弹出了“hello world”,成功的显示了其父函数中声明的name变量的值。
嵌套的函数可以访问其外部声明的变量
2.将函数与其所操作的某些数据关联起来,通常,你使用只有一个方法的对象的地方,都可以使用闭包
在 Web 中,你想要这样做的情况特别常见。大部分我们所写的 JavaScript 代码都是基于事件的 — 定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常作为回调:为响应事件而执行的函数。
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<style type="text/css">
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
</style>
<body>
<h1>hello world</h1>
<h2>hello world</h2>
<button id="a">12</button>
<button id="b">18</button>
<button id="c">22</button>
<script type="text/javascript">
document.getElementById("a").onclick = setSize(12);
document.getElementById("b").onclick = setSize(18);
document.getElementById("c").onclick = setSize(22);
function setSize(fontSize){
return function(){
document.body.style.fontSize = fontSize + 'px';
}
}
</script>
</body>
</html>
3.用闭包模拟私有方法
编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。
而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。
在之前的示例中,每个闭包都有它自己的词法环境;而这次我们只创建了一个词法环境,为三个函数所共享:Counter.increment,Counter.decrement 和 Counter.value。
该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。
这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。
var makeCounter = function () {
var privateCounter = 0;
function changeBy(val){
privateCounter += val;
};
return {
increment: function(){
changeBy(1);
},
decrement: function(){
changeBy(-1);
},
value: function(){
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
Counter1.increment();
console.log(Counter1.value());//1
console.log(Counter2.value());//0
请注意两个计数器 counter1 和 counter2 是如何维护它们各自的独立性的。每个闭包都是引用自己词法作用域内的变量 privateCounter 。
每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。
以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。
4.循环里的闭包
怎么才能实现输出0-5呢
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000 * i);
}//55555
//方法一,makeCallback函数为每一个回调创建一个新的词法环境。
function makeCallback(i) {
return function() {
console.log(i)
};
}
for(var i=0;i<10;i++){
setTimeout(makeCallback(i),1000)
}
//另一种方法使用了匿名闭包
for(var i=0;i<10;i++){
(function(i){
setTimeout(function () {
console.log(i)
},1000)
})(i)
}
//使用let声明变量
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000 * i);
}