1、什么是闭包?
闭包就是可以访问其他函数内部变量的函数,举个例子,一个内部函数总是可以访问其所在的父函数中声明的参数和变量,那么这个内部函数就是闭包。
2、闭包的举例
例1:
function pp() {
return function sum() {
let m=1;
return function show() {
console.log('m:'+ ++m);
};
};
}
let a=pp()()
console.log(a());//2
console.log(a());//3
console.log(a());//4
console.log(a());//5
这里,局部变量m会常驻在内存中,从而打印 2,3,4,5;
例2:
定时器与闭包
for(var i=1;i<=3;i++) {
setTimeout(function () {
console.log(i);
},1000);
}//4 4 4
这里,为什么打印4,4,4,而不是1,2,3 ???
因为setTimeout
是异步执行,1秒后往任务队列里面添加一个任务,只有主线上的全部执行完,才会执行任务队列里的任务,var是全局作用域,当主线执行完成后,i是4,所以打印4,又因为每一次for循环的时候,setTimeout
都执行一次,所以for循环3次,打印3个4。
我想让它打印1,2,3,有什么方法吗?
法(1):
for(let i=1;i<=3;i++) {
setTimeout(function () {
console.log(i);
},1000);
}//1 2 3
只是将var 变成了let,通过使用 let 来声明块变量,这时候变量就能作用于这个块,所以 function就能使用 i 这个变量了。
法(2):
闭包作为参数传递
for(var i=1;i<=3;i++) {
(function(a) {
setTimeout(function () {
console.log(a);
},1000);
})(i);
}//1 2 3
这里给function变成立即执行函数(声明一个函数,并马上调用这个匿名函数就叫做立即执行函数;)立即执行函数内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
例3:
返回年龄在10-20之间的数组(filter函数)
let arr = [
{
name: 'zs',
age: 5
},
{
name: 'ls',
age: 16
},
{
name: 'ww',
age: 20
}
]
function between(a,b) {
return function(v) {
return v.age>=a && v.age<=b;
}
}
console.table(arr.filter(between(10,20)))
结果:
例4:
函数作为返回值
let pp = {
user: "小明",
get: function() {
return function() {
return this.user;
}
}
}
let a=pp.get();
console.log(a());//undefined
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象。
这里this指向window,而此时window里面没有定义user。
怎样打印出“小明”呢???
法(1):
将this保存在一个变量that中 let that = this;
let pp = {
user: "小明",
get: function() {
let that = this;
return function() {
return that.user;
}
}
}
let a=pp.get();
console.log(a()); //小明
法(2):
箭头函数
let pp = {
user: "小明",
get: function() {
//箭头函数
return () => {
return this.user;
}
}
}
let a=pp.get();
console.log(a()); //小明
3、闭包的特点
- 函数嵌套函数
- 参数和变量不会被垃圾回收机制回收(局部变量会常驻在内存中)
- 内部函数总是可以访问其所在的外部函数中声明的参数和变量
4、闭包的优点和缺点
- 优点:变量长期驻扎在内存中,避免全局变量的污染。
- 缺点:由于闭包携带包含它函数的作用域,因此比其他函数占用的内存更多,泄露内存。
5、使用注意点
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。