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

1、什么是闭包?

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

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

闭包的官方解释:

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 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怎么办呢?这个时候你就需要知道闭包怎么用了。

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

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 

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

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指向问题:

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

2. 闭包可以使得函数内部的值可以在函数外部进行修改。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

垃圾回收机制:

JS中的垃圾回收器会定期扫描内存中的对象,标记那些可达对象和不可达对象。

        可达对象指的是当前代码中正在被使用的对象
        不可达对象指的是已经不再被引用的对象。
垃圾回收器会将不可达对象标记为垃圾对象,并将它们从内存中清除。

JavaScript中的垃圾回收机制主要有两种

标记清除(Mark-and-Sweep)和引用计数(Reference Counting)。
标记清除是JavaScript中主流的垃圾回收算法,而引用计数则已经很少被使用。

引用计数对于循环引用会造成内存泄漏。

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值