在理解闭包是个什么玩意儿之前,我们先来看看JavaScript中的变量作用域,因为闭包和它有着紧密联系。
在JavaScript中,变量根据作用域的不同分为两种:全局变量和局部变量。
-
函数内部可以使用全局变量。
-
函数外部不可以使用局部变量。
-
当函数执行完毕,本作用域内的局部变量会销毁。
什么是闭包?
在《 JavaScript 高级程序设计 》一书中,给出了闭包的概念:闭包(closure)指有权访问另一个函数作用域中变量的函数 。
访问另一个函数作用域中的变量的函数,那还不好理解吗,最常用的闭包——就是在一个函数内部创建另一个函数:
function fn1(){
var num = 10;
function fn2(){
console.log(num); // 10
}
fn2();
}
fn1();
不信的话,可以打开chrome浏览器调试闭包:
-
打开浏览器,按 F12 键启动 chrome 调试工具。
-
设置断点。
-
找到 Scope 选项(Scope 作用域的意思)。
-
当我们重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。
-
当执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。
闭包的作用?
闭包的主要作用: 延伸了变量的作用范围。
function fn(){
var num=10;
return function(){
console.log(num);
}
}
var f = fn();//这里相当于f=function(){console.log(num)}
f();//这里即调用了匿名函数
看上面这段代码,我们说当函数执行完毕,本作用域内的局部变量会销毁,但是调用了fn()之后,很明显num还需要在调用 f() 时发挥作用(我还没有被打印,你怎么能说销毁我!!)
闭包的几个小案例
案例1 点击li输出索引号
有这么一个列表:
<ul>
<li>点我是0</li>
<li>点我是1</li>
<li>点我是2</li>
<li>点我是3</li>
</ul>
要实现点击li输出对应的索引号,聪明人都知道直接这样是行不通的:
window.onload = function(){
var lis = document.querySelectorAll("li");
for (var i=0;i<lis.length;i++){
lis[i].onclick = function(){
console.log(i);
}
}
};
这样的代码最终不管点击哪个li,输出的索引都是4。这和 var 的变量声明有关,如果是ES6中的 let 来声明 i ,那么问题迎刃而解。
但是!任性的我就是想用var来声明呢!?
在以前我们通过给每个li添加自定义的index属性来实现:
window.onload = function(){
var lis = document.querySelectorAll("li");
for (var i=0;i<lis.length;i++){
lis[i].index = i;
lis[i].onclick = function(){
console.log(this.index);
}
}
};
虽然这种方法也完美实现,但是不知好歹地给人家加了新属性太说不过去。学了闭包之后,我们就可以这么实现:
window.onload = function(){
var lis = document.querySelectorAll("li");
for (var i=0;i<lis.length;i++){
(function(i){
lis[i].onclick = function(){
console.log(i);
}
})(i);
}
};
通过立即执行函数(function(){})() 传入参数i,每一次循环都会产生一个立即执行函数。
案例2 定时器中的闭包——3秒之后打印出li的所有内容
代码实现:
window.onload = function(){
var lis = document.querySelectorAll("li");
for (var i=0;i<lis.length;i++){
(function(i){
setTimeout(function(){
console.log(lis[i].innerHTML);
},3000);
})(i);
}
};
案例3 计算打车价格
需求:
-
打车起步价13元(3公里内), 之后每多一公里增加5块钱.
-
用户输入公里数就可以计算打车价格
-
如果有拥堵情况,总价格多收取10块钱拥堵费
代码实现:
var carPrice = (function(){
//起步价
var start = 13;
//总价
var total = 0;
return {
price: function(n){
if (n<=3){
total = start;
} else {
total = (n-3)*5 + start;
}
return total;
},
yd: function(flag){
return flag ? total + 10 : total;
}
}
})();
console.log(carPrice.price(5));
console.log(carPrice.yd(true));
console.log(carPrice.price(2));
console.log(carPrice.yd(false));