闭包概念
简单讲,闭包就是指有权访问另一个函数作用域中变量的函数。个人认为,理解闭包的关键在于:外部函数调用之后其变量本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量,这就是闭包的重要概念。
那为什么是说:有权访问另一个函数作用域中的变量呢,难道函数的作用域不是谁都可以访问的吗?答案确实是不是谁都有权访问的。在JS中, 全局变量有全局作用域,函数内声明的变量有局部作用域,局部作用域之间是无法互通的,JS在运行每个函数时,都会为该函数创建执行环境,即所说的作用域,在这个环境中,只有该作用域中的函数有权访问该作用域中的变量,因此,有权访问另一个函数作用域中变量的函数肯定是函数中嵌套着函数这种情况下了。
闭包现象
正常情况下,当函数执行完后,JS会销毁当前函数执行环境和调用对象,等待垃圾回收机制将函数中用过且没有后续引用的变量回收,但如果出现以下情况:
let fn = ()=>{
let a = 0;
return ()=>{
console.log(a + 1);
}
};
let bar = fn();
bar();
现在出现了嵌套函数,fn函数执行后其作用域被销毁,但返回的函数内部依赖着fn作用域中的变量a,bar函数的执行需要该变量,因此该变量不会被销毁,仍保存在内存中,这种现象就是闭包现象。
闭包的应用
- for循环中 我们来看一下下面这种情况:
for(var i=0;i<5;i++){
setTimeout(()=>{
console.log(i);
},1000)
}
上述代码执行后可以发现,若不用let来定义i,打印出的都是5 5 5 5 5,这是因为var是全局变量,setTimeout是宏任务,所以在执行定时器的时候,for循环已经执行结束,所以打印出的都是5。那么其实用闭包可以解决这个问题:
for(var i=0;i<5;i++){
(function(i){
setTimeout(()=>{
console.log(i);
},1000)
})(i);
}
执行上述代码后可以发现,打印出的是0 1 2 3 4;说明闭包可以解决这个问题
- 单例模式 请看下面代码:
let singleton = (function(){
let age = 18;
let speak = ()=>{
console.log('speaking');
};
return {
name: 'liang',
getAge: ()=>{
return age;
}
}
})()
上述代码就创建了一个单例,匿名函数最大的用途就是创建闭包,并且可以构建命名空间,用来减少全局变量,使闭包模块话代码,减少变量的污染,尤其是一个项目中多人协同开发的场景中,很多框架中都会利用匿名函数自执行方法。
- 柯里化
function curry(fn) {
let arg=[];
return function () {
if(arguments.length===0){
fn.apply(null,arg);
}else{
let arr=Array.from(arguments);
arg=arg.concat(arr);
}
}
}
function getSum() {
let arr=Array.from(arguments);
let sum=arr.reduce(function (sum,item) {
return sum+item;
});
console.log(sum);
}
let fn=curry(getSum);
fn(10);
fn(20);
fn(30);
fn();
上述案例,运行一个函数,如果传参,将参数存储,如果不传参,将存储的参数累加并打印。柯里化函数利用了闭包的特点,传入的参数会做存储留用,等到参数传完了,再对参数做处理
闭包的优点
- 闭包可以使函数的作用域延长,以至于可以在外部访问到函数内部的变量
- 闭包可以维持一个变量一直在内存中,不会被垃圾回收机制回收
- 闭包可以用来减少全局变量的污染
闭包的缺点
- 使用闭包不当,会导致内存泄露
- 内存消耗很大,所以不能滥用闭包
- 我们使用闭包之后,要清除这个闭包
闭包的this指向
我们先来看一下代码:
function fn(){
return function(){
console.log(this);
}
}
fn()();
执行上述代码,可以发现,打印出的是window对象,可见闭包中的内部函数this指向的是window
结束语
好啦,那么关于闭包的知识我们就先记录到这啦,闭包在实际开发过程中如何使用,还是需要结合场景来的,运用合理会大大提高业务能力,但倘若运用不当,也会对性能造成很严重的消耗哦,请谨慎哦~