闭包翻译自英文单词 closure,这个概念第一次出现在 1964 年的《The Computer Journal》上,由 P. J. Landin 在《The mechanical evaluation of expressions》一文中提出了closure 的概念。
为了想要一个私有变量或者方法,通过函数内部访问函数外部作用域时会产生闭包,
优点:实现数据与函数隔离。避免变量在该函数作用域外被全局污染;
缺点:闭包内部的变量会常驻内存不会被垃圾回收机制销毁;
对于闭包的内存泄漏说法:
先理解内存泄露
百度百科:内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
js程序员:内存泄露是指你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来。
回归到主要矛盾上,为什么会流传闭包会导致内存泄露!因为IE浏览器早期的垃圾回收机制,有 bug。
IE浏览器中使用完闭包之后,依然回收不了闭包里面引用的变量。
在IE浏览器中,由于BOM和DOM中的对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用造成的内存泄露在本质上也不是闭包造成的。
闭包大的用途:
1匿名自执行函数,保存变量在内存中:
function closure(){
var count = 0
return function (){
return console.log(count++)
}
}
var cloFun = closure()
cloFun() //0
cloFun() //1
//函数执行返回自己的匿名函数,将私有变量保存起来,缺点就是耗费内存
2通过闭包来实现单例模式
var SingleStudent = (function () {
function Student() {}
var _student;
return function () {
if (_student) return _student;
_student = new Student()
return _student;
}
}())
var s = new SingleStudent()
var s2 = new SingleStudent()
function Person(){
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
};
var john = new Person();
console.log(john.getName());
john.setName("john");
console.log(john.getName());
var jack = new Person();
console.log(jack.getName());
jack.setName("jack");
console.log(jack.getName());
//运行结果:
// default
// john
// default
// jack
john和jack都可以称为是Person这个类的实例,因为这两个实例对name这个成员的访问是独立的,互不影响的。
经典面试题,循环中使用闭包解决 `var` 定义函数的问题
for( var i = 0; i < 5; i++ ) {
setTimeout(() => {
console.log( i );
}, 1000 * i)
}
这是一道作用域相关的经典笔试题,需要实现的功能是每隔 1 秒控制台打印数字 0 到 4。但实际执行效果是每隔一秒打印的数字都是 5,为什么会这样呢?
如果把这段代码转换一下,手动对变量 i 进行命名提升,你就会发现 for 循环和打印函数共享了同一个变量 i,这就是问题所在。
var i;
for(i = 0; i < 5; i++ ) {
setTimeout(() => {
console.log(i);
}, 1000 * i)
}
要修复这段代码方法也有很多,比如将 var 关键字替换成 let,从而创建块级作用域。
for(let i = 0; i < 5; i++ ) {
setTimeout(() => {
console.log(i);
}, 1000 * i)
}
//或者
for(var i = 0; i < 5; i++ ) {
let _i = i
setTimeout(() => {
console.log(_i);
}, 1000 * i)
}
这里补充一点
如果只是执行一个函数内部的闭包,根据v8引擎垃圾回收机制,闭包函数执行完,没有其他地方再引用他,所以就执行销毁了。这么写的意思是 调用几次形成了几次的闭包,并不是一个闭包执行了几次。
改成 绑定在dom事件里,就会存下这个引用,相当于一个变量
document.querySelector('#flush').addEventListener('click',deb())
function deb(){
let time = null
return function (args) {
if(time){
console.log(time,2)
time = null
}
time = 4
}
}
//4 2
常规使用变量引用函数,最后在使用完毕后销毁变量
var a = function deb(){
let time = null
return function (args) {
if(time){
console.log(time,2)
time = null
}
time = 4
}
}
var setInt = setInterval(a(),1000)
//在不再使用闭包的情况下 销毁变量
a = null
clearInterval(setInt)
setInt = null