闭包的简单讲解
什么是闭包
要理解闭包,首先来看下下面这段代码:
var count = 1;function add(){count++;console.log(count);
}add(); // 2
add(); // 3
add(); // 4
可以看到这里调用了3次函数,count的值也从1增长到了4,但是这么写会导致全局变量被污染,所以将count的定义移动到add函数内部,代码如下:
function add() {var count = 1;count++;console.log(count);
}add(); // 2
add(); // 2
add(); // 2
但是这又导致了另一个问题,变为局部变量的count不会自增了,所以那么就可以利用闭包的这个特性将每次调用时的count保存起来这样就可以实现变量的自增了,代码如下:
function add() {var count = 1;return function(){count++;console.log(count);}
}let getCount= add();
getCount(); // 2
getCount(); // 3
getCount(); // 4
可以这样理解,通过将add函数赋值给getCount这个变量,可以看作如下代码
let getCount= function(){count++;console.log(count);
}
每当调用getCount()函数的时候,首先要获取count变量,因为JavaScript
中存在作用域链的关系,所以会从add函数下得到对应的count,因为闭包存在着闭包可以访问到父级函数的变量,且该变量不会销毁的特性所以上次的变量会被保留下来,所以可以做到自增的实现。
闭包的特性
根据以上对闭包的讲解,我们可以总结出闭包的特性
-
闭包可以访问到父级函数的变量
-
访问到父级函数的变量不会销毁
闭包的用途
1.封装私有变量
闭包可以用于封装私有变量,以防止其被外部访问和修改。封装私有变量可以一定程度上防止全局变量污染,使用闭包封装私有变量可以将这些变量限制在函数内部或模块内部,从而减少了全局变量的数量,降低了全局变量被误用或意外修改的风险。
function add(){let count = 0function a(){count++console.log(count);}return a
}
var res = add()
res() //1
res() //2
res() //3
在上面的代码示例中,add函数返回了一个闭包a,其中包含了count变量。由于count只在add函数内部定义,因此外部无法直接访问它。但是,由于a函数引用了count变量,因此count变量的值可以在闭包内部被修改和访问。这种方式可以用于封装一些私有的数据和逻辑。
2.做缓存
函数一旦被执行完毕,其内存就会被销毁,而闭包的存在,就可以保有内部环境的作用域。
function foo(){var myName ='张三'let test1 = 1const test2 = 2 var innerBar={getName: function(){console.log(test1);return myName},setName:function(newName){myName = newName}}return innerBar
}
var bar = foo()
console.log(bar.getName()); //输出:1 张三
bar.setName('李四')
console.log(bar.getName()); //输出:1 李四
这里var bar = foo() 执行完后本来应该被销毁,但是因为形成了闭包,所以导致foo执行上下文没有被销毁干净,被引用了的变量myName、test1没被销毁,闭包里存放的就是变量myName、test1,这个闭包就像是setName、getName的专属背包,setName、getName依然可以使用foo执行上下文中的test1和myName。
闭包的缺点
闭包会导致变量不会被垃圾回收机制回收,造成内存消耗以及对于不恰当的使用闭包可能会造成内存泄漏的问题
如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。
内存泄漏的解决方案
this.name = 'WindowName'
let myObj = {name: 'beast senpai',get: function(){return function(){console.log(this); // WindowNamereturn this.name;}}
}let myObjname = myObj.get()();//先调用 myObj.get(),再调用它返回的结果
console.log(myObjname); // WindowName
这里发生了内存泄漏使得this
指向了Window
对象
解决方案1:在get
函数使用that
保存此时的this
this.name = 'WindowName'
let myObj = {name: 'beast senpai',get: function(){let that = this;return function(){console.log(that); // myObjreturn that.name;}}
}let myObjname = myObj.get()();
console.log(myObjname); // beast senpai
解决方案2:将get
函数的返回值改回使用箭头函数的方式做返回
this.name = 'WindowName'
let myObj = {name: 'beast senpai',get: function(){return ()=>{console.log(this); // myObjreturn this.name; }}
}let myObjname = myObj.get()();
console.log(myObjname); // beast senpai
消除闭包
不用的时候解除引用,避免不必要的内存占用
取消fn
对外部成员变量的引用,就可以回收相应的内存空间。
function add() {var count = 0return function fn() {count++console.log(count)}
}var a = add() // 产生了闭包
a() // 1
a() // 2
a = null // 取消 a 与 fn 的联系,这个时候浏览器回收机制就能回收闭包空间
参考
JavaScript | 闭包
什么是闭包?(详解闭包)