前言
闭包是JS的一个难点也是它的一个特色,理解闭包非常重要,我们必须掌握。那么什么是闭包呢?它又有什么作用呢?下面我们一起来寻找答案!
问题1:什么是调用栈
在理解闭包之前,我们先了解下什么是调用栈。
所谓的调用栈,是用来管理函数调用关系的一种数据结构。当一个函数执行完毕后,该函数的执行上下文就会被销毁(出栈)。
请看下面一个案例:
var a=2
function add(){
var b=10
return a+b;
}
add()
我们知道V8引擎会维护一个调用栈,代码执行前进行编译,该案例的调用栈如下所示:
解释一下:变量环境和词法环境并没有本质的区别,只是为了方便区分var和let、const。 变量环境: 通过var声明或者function(){}声明变量存放在这里,用来提升;词法环境: 通过let、const创建的变量存放在这,不会提升。
问题2:什么是闭包
-
定义: 当通过调用外部函数A返回的内部函数B后,即使外部函数已经执行结束了,但是 内部函数引用了外部函数的变量依然会保存在内存中,我们把这些变量的集合,称为闭包。
-
形成: 当函数A将内部函数B返回出来调用时
案例1:
function a(){
function b(){
var bbb=234
console.log(aaa); //123
}
var aaa=123
return b
}
var demo=a() //函数a的调用返回得到函数b //闭包
demo() //调用函数b
此调用栈为:
a的执行带来了b的创建,b函数在a函数里面,所以b函数可以访问a函数的执行上下文。从上到下进行调用。
可以看到函数a返回的是函数b,在全局作用域下调用了函数a,并把返回的值给了变量demo,然后进行调用,也就是函数b的调用,在函数b内用到了外部函数a的变量aaa,在函数a调用结束后该函数执行上下文会销毁,但会保留一部分留在内存中供函数b使用,这就形成了闭包。
案例2:
function foo(){
var myName ='好看'
let test1=1
const test2=2 //let const 词法环境里的
var innerBar={
getName: function(){
console.log(test1);
return myName
},
setName:function(newName){
myName=newName
}
}
return innerBar
}
var bar=foo() //innerBar foo执行完后销毁,但有小部分保存在内部
bar.setName('计划')
console.log(bar.getName());
此调用栈为:
var bar=foo() foo执行完后销毁,但有小部分保存在内部。本来foo函数执行完后应该被销毁,但是因为形成了闭包,还有其他内部函数对其变量的访问(setName、getName),所以强制导致foo执行上下文无法彻底销毁,在内存中会有小部分closure(闭包)——其中存放的就是被用到的变量myName、test1
closure(闭包)就像一个setName、getName的专属背包,只有它们可以访问,其他方法都不能访问,所以我们把这个背包称为是foo函数的闭包。
问题3:闭包有什么作用
- 模块化开发(实现公有变量)
案例1:
function add(){
let count=0
count++
console.log(count);
}
add()
add()
add()
add()
会发现打印输出了4次1,但是我们想要实现的是累加效果,也不想用全局变量,那么我们怎么实现这个效果呢?结果就是用到闭包!
function add(){
let count=0
// count++
// console.log(count);
function a(){
count++
console.log(count);
}
return a
}
var res=add() //把函数a赋值给res变量
// add()
// add()
// add()
res() //1 调用了函数a
res() //2
res() //3
res() //4
- 做缓存
- 封装私有化属性
- 防止全局变量污染
问题4:闭包有什么缺点
- 一旦形成闭包,只有在页面关闭后闭包占用的内存才会被收回,所以造成了内存泄漏。
思考题:
function test(){
var arr=[] //arr[0]=function(){},arr[1]=function(){},arr[2]=function(){}...
for(var i=0;i<10;i++){
arr[i]=function(){
console.log(i);
}
}
return arr
}
var myArr=test() //调用函数test,返回的是arr数组
for(var j=0;j<myArr.length;j++){
myArr[j]();
}
打印出来的是10次10,如果我们想要打印出来0-9的连续数,那么怎么实现呢?怎么利用闭包的方法实现?
思路: 可以利用自执行函数并形成闭包。for循环里的变量i是在test函数里不是在内部function内,相当于一个公有的变量i,最后的值都是循环结束后i的值。所以这样的闭包形成的是公用的i。
解决办法: 在arr[i]=function(){ console.log(i); }外再加一层闭包——可以用一个自执行函数把它包起来
function test(){
var arr=[]
for(var i=0;i<10;i++){
(function(j){
// var j=0
arr[j]=function(){
console.log(j);
}
})(i)
}
return arr
}
var myArr=test()
for(var j=0;j<myArr.length;j++){
myArr[j]();
}
结语
本篇文章就到此为止啦,由于本人经验水平有限,难免会有纰漏,对此欢迎指正。如觉得本文对你有帮助的话,欢迎点赞收藏❤❤❤,写作不易,持续输出的背后是无数个日夜的积累,您的点赞是持续写作的动力,感谢支持。