什么是闭包?「前端每日一题v22.11.8」
在javascript中,经常听见闭包,那什么是闭包,闭包有哪些用途?
闭包就像它的字面意思一样,表示一个封闭的内存空间,就叫闭包。换言之,当每个函数被创建的时候,都有一个闭包。
在js中有全局变量和局部变量,在函数内部可以读取函数外部作用域,函数外部无法直接读取函数内部变量。因此一个函数可以形成一个闭包
可以通过代码来具体展示一下什么是闭包
function init() {
var name = "init"; // name 是一个被 init 创建的局部变量
function alertName() { // alertName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
alertName();
}
init(); // 弹出init
最终弹出init,但是init函数内部的name变量在函数外部是访问不到的,相信大家都理解。这个没有什么问题,我们再来一个例子
function init() {
var name = "init";
function alertName() {
alert(name);
}
return alertName;
}
var fun = init();
fun() // 同样弹出init
这个同样弹出init,同上一个例子的区别就是init函数将内部的alertName函数导出。闭包是由函数以及声明该函数的「词法环境」组合而成的,这个环境包含了闭包「创建时」作用域内的「任何局部变量」,这个例子中fun是init内部函数alertName的引用,alertName实例维持了对于name的引用,所以fun调用时,name仍然可用
借用「MDN」上的内容,闭包有什么用呢?
我们可以定义一个公共函数,令其可以「访问私有函数和变量」
var Counter = (function() {
var num = 0;
function change(val) {
num += val;
}
return {
incre: function() {
change(1);
},
decre: function() {
change(-1);
},
value: function() {
return num;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.incre();
Counter.incre();
console.log(Counter.value()); /* logs 2 */
Counter.decre();
console.log(Counter.value()); /* logs 1 */
这里我们使用自执行函数创建了一个「词法环境」,然后再利用闭包暴露出来到Counter上
当然我们也可以不通过自执行函数创建多个计数器
var makeCounter = function() {
var num = 0;
function change(val) {
num += val;
}
return {
incre: function() {
change(1);
},
decre: function() {
change(-1);
},
value: function() {
return num;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.incre();
Counter1.incre();
console.log(Counter1.value()); /* logs 2 */
Counter1.decre();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
Counter1和Counter2都是维护的自己的计数逻辑,都具有各自的独立性
「闭包的缺点」
闭包常见的一个问题就是for循环的问题,很多新手总是不理解以下代码输出什么
for (var i = 0; i < 4; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 结果是4个4,1s过后,词法环境的i都已经变成了4,解决方法是使用let或者闭包
闭包会使函数变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成性能问题,IE中可能导致内存泄露。
那解决方法是什么呢?「就是在退出函数之前,将不使用的局部变量删除」