前言
大家好,我是珂圩
闭包是一种特殊函数,但是对于闭包的负面新闻也是有不少。
为什么闭包那么容易招黑呢?
本篇文章就带你了解一下闭包的本质以及合理的使用闭包
什么是闭包
闭包:也就是让你可以在一个内层函数中访问到外层函数的作用域 在javaScript中,每创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁
js
function init() {
var name = 'Mozilla' //name 是一个被init创建的局部变量
function dispalyName (){ //displayName() 是内部函数,一个闭包
alert(name) //使用了父函数中声明的函数
}
dispalyName()
}
init()
可能有些朋友比较对于闭包定义还是比较模糊,用一句通俗的话来说就是可以访问外部函数的变量
来几个例子看一下
闭包的其中一种形式,在displayName
函数中没有使用name
变量,但是dispalyName
中是可以访问到的
注:不一定要使用外部函数的变量才叫闭包,而是可以访问到外部函数就可以成为闭包函数
js
function init() {
var name = 'Mozilla'
function dispalyName (){}
dispalyName()
}
init()
下面这种函数也可以称作为闭包,虽然函数dispalyName
没有在init
中定义,但是通过穿参的方式displayName
也可以获取到init
的内部变量。
js
function init() {
var name = 'Mozilla'
dispalyName(name)
}
function dispalyName (name){}
init()
使用场景
任何闭包的使用场景都离不开这两点
- 创建私有变量
- 延长变量的生命周期
一般函数的词法环境在函数返回就会被销毁,但是闭包函数会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的声明周期的目的
词法环境:每当创建一个函数时,都会创建一个新的词法环境。这个词法环境会记录函数执行时所处的作用域以及函数中定义的变量和函数。当函数执行完成后,它的词法环境会被销毁。但是闭包是一个特殊情况。
柯里化函数
柯里化的目的在于避免频繁调用具有享用参数函数的同时,又能够轻松的重用
js
function addition (a,b){
return a + b
}
// 其中某一个参数 经常会一样
addition(1,2)
addition(1,3)
addition(1,4)
// 我们可以使用闭包柯里化计算
function aadition (a){
return b =>{
return b
}
}
let aAddition = addition(1)
bAddition(2)
bAddition(3)
bAddition(4)
//这样就可以 轻松复用
闭包模拟私有方法
js
var fun = function (){
var a = 0
function change(val){
a += val
}
return {
increment:function(){
change(1)
},
decrement:function(){
change(-1)
}
value:function(){
return a
}
}
}
var fun1 = fun()
var fun2 = fun()
fun1.value()//0
fun1.increment()
fun1.value()//1
fun2.value()//0
//所以说会存在两个独立的作用域 也可以称做模块方式
非必要情况下并不需要使用闭包 因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响
什么时候使用呢?
就是当前的需求需要使用到闭包的特性,创建私有变量 或者延长变量生命周期
函数中创建函数的弊端
对于函数中直接创建函数是不建议的,因为每个对象对象创建时方法都会重新被创建
当你在一个函数中定义另一个函数时,每次调用外部函数时都会创建一个新的内部函数实例 。这意味着,如果你多次调用外部函数,就会创建多个相同的内部函数实例,每个实例都占用内存空间。
有一个很好的例子可以说明这一点就是:vue中的data为什么是一个函数而不是一个对象 ,原理是一样的就是因为他想要隔绝变量、独立作用域,所以说如果你不需要这个特性的话就避免这样使用,可以将方法挂载到原型链上面。object.prototype.function
js
function outerFunction() {
function innerFunction() {
console.log('Inner function called');
}
return innerFunction;
}
var fn1 = outerFunction();
var fn2 = outerFunction();
console.log(fn1 === fn2); // false
错误示范
下面再给大家举几个闭包使用错误的例子,看看你有没有中招!!!
过渡使用闭包
在这个例子中,闭包中保持了对外部变量 data
的引用 ,使得外部函数中的 data
无法被释放,可能导致内存泄漏。
js
function outerFunction() {
var data = 'sensitive data';
function innerFunction() {
console.log(data);
}
return innerFunction;
}
var closure = outerFunction();
事件监听器未正确移除
在这个例子中,忘记移除事件监听器会导致闭包中保持对 DOM 节点的引用,使得 DOM 节点无法被垃圾回收,从而造成内存泄漏
js
function addEventListener() {
var element = document.getElementById('myButton');
element.addEventListener('click', function handleClick() {
console.log('Button clicked');
});
}
addEventListener();
循环中的闭包问题
在这个例子中,闭包中捕获的是循环结束后的 i
的值,而不是循环中每次迭代时的 i
的值。因此,无论调用哪个函数,都会输出循环结束后的 i
的值
js
function createFunctions() {
var result = [];
for (var i = 0; i < 5; i++) {
result.push(function() {
console.log(i);
});
}
return result;
}
var functions = createFunctions();
functions[0](); // 输出 5 而不是期望的 0
上面这个例子有些朋友可能不太理解,一句话解决你们的疑惑,就是闭包是访问外部函数的变量并不是在自己的函数内创建一个新的变量 ,所以说要说是访问,大家访问到的都是一样的,永远访问到的是i
的最终值。如何解决这个问题呢
js
function createFunctions() {
var result = [];
for (var i = 0; i < 5; i++) {
(function(index) {
// 在每次迭代中创建一个闭包作用域,并捕获当前循环变量的值
result.push(function() {
console.log(index);
});
})(i); // 将当前循环变量传递给立即执行函数
}
return result;
}
var functions = createFunctions();
functions[0](); // 输出 0
朋友们可以看懂吗?上面说过闭包的特性就是私有化变量,所以上述例子就是给每个函数增加一个闭包作用域,如果还不明白你可以查看上面的闭包模拟私有方法那一块,一样的原理只是变了一个形式而已。
结语
文章中提到的技术点,如果有不懂得或者想要了解更多的评论区告诉我,我会针对性的出一期更详细的文章。
如果此文章对你有帮助,点个赞支持一下
后续还会不定时出一些文章或针对某个领域的系列文章
有兴趣的可以关注一下!