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

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循环执行的过程当中,每次都只是向数组中添加了一个匿名函数,但是你要知道这个时候这些函数并没有自我执行。当你通过b[i]()调用匿名函数的时候,通过闭包获得的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),这时一定要小心,不要随便改变父函数内部变量的值。
相关推荐
南北是北北2 小时前
JetPack WorkManager
面试
uhakadotcom3 小时前
在chrome浏览器插件之中,options.html和options.js常用来做什么事情
前端·javascript·面试
想想就想想3 小时前
线程池执行流程详解
面试
程序员清风4 小时前
Dubbo RPCContext存储一些通用数据,这个用手动清除吗?
java·后端·面试
南北是北北4 小时前
JetPack ViewBinding
面试
南北是北北5 小时前
jetpack ViewModel
面试
渣哥5 小时前
Lazy能否有效解决循环依赖?答案比你想的复杂
javascript·后端·面试
前端架构师-老李6 小时前
面试问题—你接受加班吗?
面试·职场和发展
ANYOLY6 小时前
多线程&并发篇面试题
java·面试
南北是北北7 小时前
RecyclerView 的数据驱动更新
面试