引言
闭包,这个神秘的概念,在编程世界中扮演着重要的角色。它既是一种技术,也是一种思维方式。闭包的魅力在于它能够捕获上下文并延长其存在,使得代码能够更加灵活和强大。无论是在函数式编程还是异步编程中,闭包都展现出了其独特的价值。这篇文章将带领大家揭开闭包的神秘面纱。
思考题
掘友们先来思考这段代码最后会输出什么
js
var arr = []
for(var i = 0;i<10;i++){
arr[i] = function(){
console.log(i);
}
}
for(var j = 0;j<arr.length;j++){
arr[j]()//10 10 10 10 10 10 10 10 10 10
}
这还不简单?那不是输出(0-9)嘛?事实真的是这样吗?我们可以看到,在for循环中定义了一个全局变量i,在运行完第一个for循环以后,此时 i=10,所以当运行第二个for循环时,会打印10个10。
那我们要怎么样才能打印出(0-9)呢?
1.使用let
js
var arr = []
for(let i = 0;i<10;i++){
arr[i] = function(){
console.log(i);
}
}
for(var j = 0;j<arr.length;j++){
arr[j]()//0 1 2 3 4 5 6 7 8 9
}
let 让for循环变成块级作用域,于是就能实现输出(0-9)了
2.闭包
使用闭包也能达到同样的效果,等掘友们看完这篇文章,我相信你一定会用闭包来实现这个效果
闭包
我们先来看看这段代码:
js
function foo(){
var myName = '旭旭'
let test1 = 1
let test2 = 2
var innerBar = {
getName: function(){
console.log(test1);
return myName
},
setName: function(newName){
myName = newName
}
}
return innerBar
}
var bar = foo()
bar.setName('77')
console.log(bar.getName());//1 77
这段代码的结果为1 77.我们来分析一下,首先在调用栈中形成一个全局执行上下文,在变量环境中存入foo,bar,outer,然后开始调用foo(),返回了一个innerBar对象给bar,在调用完foo()方法以后,foo执行上下文销毁,再调用bar.setName(),打印输出。
掘友们问:为什么我的foo执行上下文已经销毁了,而我的bar还能调用setName方法并且可以输出test1呢?原来因为这里实现了闭包,当foo销毁时,V8发现foo函数内有一个内部函数调用了foo中的属性,于是不确定test1和getName,setName是否可以销毁,于是会将他们保留下来,这样我们仍然能够调用test1和方法。
整理一下闭包的概念:
在js中根据词法作用域中,内部函数总是可以访问其外部函数中声明的变量,当内部函数被返回到外部函数之外时,即使外部函数执行结束了,但是内部函数引用了外部函数的变量,那么这些变量依旧会保存在内存中,我们把这些变量的集合称为闭包
我们再来聊聊闭包的优缺点:
1.优点
闭包允许函数捕获并访问其声明范围外的变量,从而形成封闭的作用域。这种封装性使得我们能够隐藏特定变量,避免其被外部直接访问和修改,有利于代码的安全性和可维护性。它提供了一种灵活、安全、具有状态保持能力的编程方式,能够让我们更好地处理复杂的逻辑和需求。通过合理地运用闭包,我们能够写出更加模块化、可复用和可扩展的代码。
2.缺点
我们可以发现,在foo调用完毕后仍然可以访问foo内的属性和方法,这极易造成内存泄漏,并且闭包可能会占用更多的内存,因为它需要保留对外部环境的引用。如果闭包持有大量数据或者长期存在,可能会导致内存占用较高,需要特别注意内存管理。
解决思考题
学习完闭包以后,我们可以用这种方法来实现(0-9)的输出
js
for(var i = 0;i<10;i++) {
//自执行函数
(function a(j) {
arr[i] = function() {
console.log(j);
}
})(i)
}
在内部函数中调用了外部传入的i变量,在调用结束以后,不确定是否引用完毕,于是储存了十个i的值,再输出就可以输出(0-9)了
结尾
掘友们如此聪明,小小闭包想必也是难不倒大家,但在使用闭包时,还是要注意内存的消耗!