在介绍闭包之前我们先了解一下高阶函数吧,听起来很高大上对名字,其实我们可能经常在用,只是不知道它叫这个名字。
高阶函数是对其他函数进行操作对函数,它分为两种情况
-
将函数作为参数传递
function fn(a,b,callback) {
console.log(a+b);
callback && callback();
}
fn(1,2,function () {
alert("aa");
});
-
将函数作为返回值输出
function fn() {
return function () {
alert("bb");
};
}
fn()();
看起来是不是很熟悉,第二种方式是不是让我们想起了闭包呢,只是这个例子没有变量对引用,所以不能称之为闭包,下面我们来说一下闭包。
什么是闭包?
-
闭包是一个函数(一个函数可以访问另一个函数内部局部变量)在 javascript 中只有函数内部的子函数才可以访问该函数的局部变量,所以我们闭包的写法一般是在函数内部返回一个子函数,这个子函数使用了函数内部的变量,然后在外部调用这个子函数,这样就会使得一个函数可以访问到另一个函数内部的变量,就形成了闭包。
闭包的作用?
举例示范
-
假设页面有四个按钮,点击这四个按钮的时候输出每个按钮的索引。
// 额外附加一下for循环中let 和var 的区别
var lis = document.querySelectorAll('li')
for(var i=0;i<lis.length;i++){
lis[i].onclick=function(){
console.log(i) //每次点击都输出4
}
}
for(let i=0;i<lis.length;i++){
lis[i].onclick=function(){
console.log(i) //输出0,1,2,3
}
}
在这个例子中我们只需要把 var 改为 let 就可以正常输出了,因为for是同步的,当我们点击的时候已经执行完了,而 i 的值也会因为 var 的原因覆盖为最后的结果4,当我们使用 let 的时候,由于块级作用域的影响,导致每次迭代过程中的 i 都是独立的存在,所以就可以正常输出。
那假如我们就要使用 var 呢,怎么才可以是它正常输出呢?
-
我们可以在每个li上增加一个属性来记录 i 的值
eg:
for(var i=0;i<lis.length;i++){
lis[i].index = i;
lis[i].onclick=function(){
console.log(this.index)
}
}
-
我们可以立即执行函数来传递 i 的值,这个时候就用到了闭包
eg:
for (var i = 0; i < lis.length; i++) {
(function (i) {
lis[i].onclick = function () {
console.log(i)
};
})(i);
}
在这个例子中变量 i 通过立即执行函数传递进来的,它是立即执行函数的局部变量,但是在点击事件函数中使用了i, 并且点击的时候调用了这个点击事件函数,使得在立即执行函数外面的作用域可以访问到函数内部的变量,所以就形成的闭包。
下面我们来看看闭包的常用写法:
function fn() {
var num = 10;
return function () {
alert(num);
};
}
fn()();
function fn() {
var num = 10;
function fun () {
alert(num);
};
return fun
}
fn()();
return 除了可以返回函数,还可以返回对象
function fn() {
var num = 10;
return {
fun: function () {
alert(num);
},
};
}
fn().fun();
使用闭包的注意点
-
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。