闭包的特性
- 函数嵌套函数
- 函数内部可以引用函数外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
我们都知道,js 的作用域分两种,全局GO 和局部AO,基于我们所熟悉的作用域链相关知识,我们知道在 js 作用域环境中访问变量的权利是由内向外的,内部作用域可以获得当前作用域下的变量,并且可以获得当前包含当前作用域的外层作用域下的变量,反之则不能,也就是说在外层作用域下无法获取内层作用域下的变量,同样在不同的函数作用域中也是不能相互访问彼此变量的。
那么我们在一个函数内部也有权限访问另一个函数内部的变量该怎么办?------闭包就是用来解决这一需求的,闭包的本质就是在一个函数内部创建另一个函数。
本文以闭包两种的主要形式来学习
一、函数作为返回值
function a(){
var name = 'ddd';
return function(){
return name;
}
}
var b = a();
console.log(b()); // ddd
在这段代码中,a() 中的返回值是一个匿名函数,这个函数在 a() 作用域内部,所以它可以获取 a() 作用域下变量 name 的值,将这个值作为返回值赋给全局作用域下的变量 b ,实现了在全局变量下获取到局部变量中的变量的值。
闭包的经典例子
function fn(){
var num = 3;
return function (){
var n = 0;
console.log(++n);
console.log(++num);
}
}
var fn1 = fn();
fn1(); // 1 4
fn1(); // 1 5
一般情况下,在函数 fn 执行完后,就应该连同它里面的变量一同被销毁,但是在这个例子中,匿名函数作为 fn 的返回值被赋值给了 fn1 ,这时候相当于 fn1 = function(){var n = 0; …},并且匿名函数内部引用 fn 里的变量 num ,所以变量 num 无法被销毁,而变量 n 是每次都被调用时新创建的,所以每次 fn1 执行完后它把属于自己的变量连同自己一起销毁,于是乎最后就剩下孤零零的 num,于是这里就产生了内存消耗的问题
又一个经典例子—闭包中的定时器
按照预期它应该依次输出1 2 3 4 5 ,而结果它输出了五次 5 ,这是为什么呢?原来由于 js 是单线程的,所以在执行 for 循环的时候定时器 setTimeout 被安排到任务队列中排队等待执行,而在等待过程中 for 循环就已经在执行, 等到setTimeout 可以执行的时候,for 循环已经结束,i 的值已经变成 5,所以打印出来五个 5,那么我们为了实现预期结果应该怎么改这段代码呢?
PS:如果把 for 循环里面的 var 变成 let ,也能实现预期结果
在这段代码中,相当于同时启动3个定时器,i*100是为4个定时器分别设置了不同的时间,同时启动,但是执行时间不同,每个定时器间隔都是100毫秒,实现了每个100毫秒就执行一次打印的效果。
二、闭包作为参数传递
在这段代码中,函数fn1作为参数传入立即执行函数中,在执行到fn2(30)的时候,30作为参数传入fn1 中,这时候if(x > num)中的num取的并不是立即执行函数中的 num ,而是取创建函数的作用域中的 num 这里函数创建的作用域是全局作用域下,所以 num 取的是全局作用域中的值 15,即 30>15,打印 30 。
总结
优点
- 保护函数内的变量安全,实现封装,放置变量流入其他环境发生命名冲突
- 在内存中维持一个变量,可以做换成(但使用多了同时也是一项缺点,消耗内存)
- 匿名自执行函数可以减少内存消耗
缺点
- 其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为 null
- 其次由于闭包涉及域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。