一文了解前端面试重点--闭包

1、什么是闭包?

闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数。

温馨提示:由于闭包所在的作用域返回的局部变量不会被销毁,所以会占用内存。过度的使用闭包会迫使性能下降,因此建议大家在有必要的情况下再使用闭包。
闭包的官方解释:

闭包 (closure)是一个函数以及其捆绑的周边环境状态(lexical environment词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

javascript 复制代码
function fn(){
    var arr = [];
    for(var i = 0; i < 3; i++){
        arr.push(function(){
            return i;
        });
    }
    return arr;
}
var b = fn();
for(var i = 0; i < b.length; i++){
    console.log(b[i]());
}

这个控制台输出的是三个3,你知道为什么嘛?

答:这是因为在第3行的这个for循环执行的过程当中,每次都只是向数组中添加了一个匿名函数,但是你要知道这个时候这些函数并没有自我执行。当你通过bi()调用匿名函数的时候,通过闭包获得的i已经是3了,所以每次输出的都是3。

那么如果你想得到0,1,2怎么办呢?这个时候你就需要知道闭包怎么用了。

方法一:(闭包+立即执行函数表达式)

javascript 复制代码
function fn(){
    var arr = [];
    for(var i = 0; i < 3; i++){
        arr.push((function(num){
            return function(){
                return num;
            };
        })(i));
    }
    return arr;
}
var b = fn();
for(var i = 0; i < b.length; i++){
    console.log(b[i]());
}

将立即执行函数表达式(IIFE) (function(num) { ... })(i) 包裹在 arr.push() 中,这样可以在每次循环时创建一个新的作用域并传入当前的 i 值作为参数 num。这样,内部的匿名函数就能够捕获并保存每次循环时的 i 值,从而避免了之前出现的当匿名函数执行时i值已经变为3的问题。

通过匿名函数的立即执行,将立即执行后返回的函数直接赋值给数组arr。每次循环即将i的值传递给num,又因为num在函数中,所以有自己的独立作用域,因此num得到的值为每次循环传递进来的i值,即0,1,2

方法二:(只是把闭包函数单独抽离出来了而已)

javascript 复制代码
function createClosure(num) {
    return function() {
        return num;
    };
}

function fn() {
    var arr = [];
    for (var i = 0; i < 3; i++) {
        arr.push(createClosure(i));
    }
    return arr;
}

var b = fn();
for (var i = 0; i < b.length; i++) {
    console.log(b[i]());
}

定义了一个 createClosure 函数,它接受一个参数 num 并返回一个闭包函数,该闭包函数可以访问并返回 num 的值。闭包可以帮助我们在每次循环中创建一个独立的作用域,并保留每次循环中变量i的值。

在JavaScript中,当内部函数引用了外部函数的变量时,会创建一个闭包,使得内部函数能够访问和保留外部函数作用域中的变量。这样,每个内部函数都有自己的 num 变量,它们分别对应每次循环中的 i 值,避免了之前版本中由于共享同一个 i 变量导致的问题。

关于使用闭包时的this指向问题:

javascript 复制代码
var color = "red";
function fn(){
    return this.color;
}
var obj = {
    color:"yellow",
    fn:function(){
        return function(){//返回匿名函数
            return this.color;
        }
    }
}
console.log(fn());//red 在外部直接调用this为window
var b = obj.fn();//b为window下的变量,获得的值为obj对象下的fn方法返回的匿名函数
console.log(b());//red 因为是在window环境下运行,所以this指缶的是window
//可以通过call或apply改变函数内的this指向
console.log(b.call(obj));//yellow
console.log(b.apply(obj));//yellow
console.log(fn.call(obj));//yellow

//或者这个保存对当前对象的引用
var color = "red";
function fn(){
    return this.color;
}
var obj = {
    color: "yellow",
    fn: function(){
        var self = this; // 保存对当前对象的引用
        return function(){
            return self.color; // 使用保存的引用来访问对象的color属性
        }
    }
}
var b = obj.fn();
console.log(b()); // 输出 "yellow"

总结:

闭包解决了以下两个问题:

  1. 可以读取函数内部的变量

  2. 让这些变量的值始终保持在内存中。不会在函数调用后被清除

但同时,闭包也有它的缺点:

  1. 由于闭包会使得函数中的变量都被保存到内存中,滥用闭包很容易造成内存消耗过大,导致网页性能问题。解决方法是在退出函数之前,将不再使用的局部变量全部删除。

闭包会导致内存泄漏。内存泄漏指的是在程序中,不再需要使用的内存没有被释放或回收的情况。当程序中的对象在不再需要时仍然占用内存空间而没有被垃圾回收机制释放,就会导致内存泄漏问题。造成内存泄漏的情况可能有:定时器未清理,闭包,对象相互引用等。

我们在使用闭包时需要特别注意内存泄漏的问题,可以用以下两种方法解决内存泄露问题:

1.及时释放闭包:手动调用闭包函数,并将其返回值赋值为null,这样可以让闭包中的变量及时被垃圾回收器回收。

2.使用立即执行函数:在创建闭包时,将需要保留的变量传递给一个立即执行函数,并将这些变量作为参数传递给闭包函数,这样可以保留所需的变量,而不会导致其他变量的内存泄漏。

  1. 闭包可以使得函数内部的值可以在函数外部进行修改。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
相关推荐
我叫黑大帅1 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
吃饱了得干活1 小时前
亿级订单表分库分表设计,从0到1全流程
java·数据库·面试
程序员爱钓鱼4 小时前
Go 开发环境安装(Windows、macOS、Linux)
后端·面试·go
触底反弹4 小时前
🤯 面试被问 AI Workflow 和 Agent 有啥区别?3 张图 + 2 段代码讲清楚!
人工智能·设计模式·面试
uzong11 小时前
一个好的面试官,应该让候选人感到被尊重
面试
触底反弹14 小时前
深入理解大模型采样:Temperature、Top-K、Top-P 的原理与实战
人工智能·算法·面试
沉默王二16 小时前
腾讯一面,我霸气反问:“你说你们在做Agent项目,说说 SubAgent、Plan 模式、Skill 调用这些你们都是怎么做的?”面试官一直在擦汗。。
面试·agent·ai编程
程序员Rock20 小时前
上位机开发-MODBUS面试常见问题
c语言·c++·面试·职场和发展·上位机
PBitW1 天前
git 中容易遗忘的点 (二) ⚡⚡⚡
前端·git·面试
浩哥学JavaAI1 天前
2026年最新AI agent面试(07)_大模型架构基础
人工智能·面试·架构