闭包是面试和开发中必定会用到的一个。
定义
有两个常见的理解方式,我个人倾向第一个理解方式,最好两个理解都掌握。
理解一:闭包就是能够读取其他函数内部变量的函数。
理解二:定义在一个函数内部的函数(多数人的理解)
本质上,闭包是将函数内部和函数外部连接起来的桥梁。
产生闭包的条件:
函数嵌套、内部函数引用了外部函数的数据(变量/函数)。
常见闭包
将函数作为另一个函数的返回值
function f1(){
var n=999;
nAdd=function(){n+=1}
//f1执行时,将nadd定义到全局中nadd,
//nadd引用了匿名函数中所指向的对象,所以f1函数执行完没有被回收,
//可以在全局中直接使用nadd函数调用(读写)
function f2(){
alert(n);
}
function f3(){
alert(n);
}
return f2;
} // f1 结束符
var result=f1(); // f1只被执行一次,所有f2只被定义一次,返回了f2函数,调用result相当于调用f2函数
result(); // 999
nAdd();
result(); // 1000
在这段代码中,result
实际上指向闭包f2函数在堆中所指向的对象。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
原因就在于f1是f2的父函数,而f2函数被赋给了一个全局变量,这导致f2所指向的对象始终在(堆)内存中,但是在栈中的变量f2会在f1执行完后被回收,而f2的存在依赖于f1,因此f1指向的对象也始终在(堆)内存中,不会在调用结束后,被垃圾回收机制回收。其中f3函数指向的对象因为没有被引用,所以f1执行完后会被回收。
注意:
在函数中的变量如果没有使用 var
定义,就变成了全局变量。其中的nAdd
就是。
将函数作为实参传递给另一个函数调用
function showDelay(msg, time){
setTimeout(function(){
alert(msg) // 闭包
}, time)
}
showDelay("kkkk", 2000)
闭包的用途
- 使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
- 让函数外部可以操作(读写)到函数内部的数据(变量、函数)
注意点:
- 函数执行完后,函数内部声明堆局部变量一般时不存在的,存在于闭包中的变量才有可能存在。
- 在函数外部不能直接访问函数内部的局部变量,需要使用闭包来让外部操作。
闭包的生命周期
闭包创建于定义函数的时期,两种情况:
- 因为函数声明提升,在执行代码前的预处理时创建闭包
function fun2(){
}
- 执行使用表达式的那一行代码时创建
nadd = function(){
}
闭包结束,采用垃圾回收,使用null操作。
闭包应用
JS模块
定义:具有特定功能的JS文件。
将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包含n个方法的函数或对象。模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能。
例子:
model.js
functino myModel(){
var msg = 'kk'
functino showUpCase(){
}
function showLowerCase(){
}
return { //返回对象方便调用
showUpCase:showUpCase
showLowerCase:showLowerCase
}
}
返回含有方法的对象,方便调用这个js模块。(记得使用script标签将其引入)
调用时只需要调用这个myModel函数
var md = myModel()
md.showUpCase()
md.showLowerCase()
model2.js 使用匿名函数方法,引入后可以直接调用。
(functino myModel(window){ //形参命名为window是为了方便代码压缩
var msg = 'kk'
functino showUpCase(){
console.log(msg)
}
function showLowerCase(){
console.log(msg)
}
window.myModel2 = { //给window添加一个属性方便调用
showUpCase:showUpCase
showLowerCase:showLowerCase
}
}
)(window) // 传入实参window
因为已经添加为window的方法,所有可以直接调用。
myModel2.showUpCase()
myModel2.showLowerCase()
闭包的缺点
函数执行完后,函数内的局部变量没有释放,占用内存时间变长,容易造成内存泄露。
解决:能不用闭包就不用闭包,及时释放。
通过实例加深对闭包的了解
var name ="the window"
var object1 = {
name:"object1"
getNameFunc:function(){
return function(){
return this.name
}
}
}
object1.getNameFunc()() // 输出the window
上面的代码没有使用闭包,因为object1.getNameFunc()只是返回了一个匿名函数代码,通过再添加一个括号来调用。全局变量相当于window的属性,所以this指向的是window。
var name ="the window"
var object2 = {
name:"object2"
getNameFunc:function(){
var that = this
return function(){
return that.name
}
}
}
object2.getNameFunc()() //输出object2
上面的代码有使用闭包,通过一个局部变量that(不是关键字于this不一样)将object2传进去,在返回的匿名函数中调用了其他函数的变量that。