茶余饭后聊聊闭包到底是个什么东东?

闭包是我们每一个前端打工人绕不开的技能点,它经常出现在:同事之间吹水,面试官刁难时等一系列‘高端局’中。时至今日,要是问你闭包到底是做什么的,你请答出个一二三吗?

在这里插入图片描述

一、有趣的作用域链

在开始聊闭包之前呢,先和大家来聊一聊JavaScript中作用域的概念,在ES5中作用域有两种:全局作用域函数级作用域,我们知道,当我们去访问一个变量的时候,JavaScript会从当前作用域一级一级的往上‘冒泡’,直到我们的window(全局),由此我们也打趣的叫作用域链,我们通过案例更好的去感受一下:

var a = 1;

function foo () {
    var a = 2;
    function poo () {
        console.log(a);
    }
    poo();
}

function zoo () {
    console.log(a);
};

foo(); // 2
zoo(); // 1
console.log(a); // 1

从案例中我们可以看到,每一层函数是一个单独作用域函数内部可以访问函数外部的变量,如果找不到就会再往上找,直到全局作用域(window)

二、堆和栈傻傻分不清

2.1 内存释放

我们再来聊一聊JavaScript中的堆内存栈内存,我们都知道引用数据类型的值存储在堆内存中(比如我们的函数),一般函数执行完成内存就会被释放掉,下面举例说明一下内存释放

  1. 全局下的变量只有当页面关闭的时候内存才会被释放。

    var obj = {
        name: 'iyongbao';
    }
    
    console.log(obj.name); // iyongbao
    
  2. 如果是函数,执行完成会立即被释放掉

    function foo () {
        var a = 2;
        console.log(a);
    }
    
    var f = foo(); // 2
    
    console.log(f); // undefined
    

2.2 内存释放(特殊情况)

正常情况下,函数执行完成会立即被释放,但是也有特殊情况,那就是如果函数执行完后,函数中的私有变量被作用域外的变量引用的时,栈内存就不会去释放栈内存中的基本值。

function foo () {
    var a = 2;
    
    function zoo () {
        a++;
        console.log(a);
    }
    
    zoo();
    
    return zoo;
}

var f = foo(); // 3
f(); // 4
f(); // 5

由此我们可以看出,foo函数执行完返回给f变量一个函数,a变量没有被释放。

三、走进闭包

3.1 闭包到底是什么

其实2.2就是一个闭包,由此我们可以总结一下闭包的概念:闭包是指能够访问一个函数内部私有变量的函数。

这里注意闭包是一个函数。

3.2 闭包是如何形成的

当函数存在外部作用域的值的引用时就会产生闭包。

var a = 3;
function foo (c) {
    var b = 5;
    function zoo () {
        console.log(a + b + c);
    }
    
    zoo();
}

foo();

3.3 闭包的作用

闭包可以使我们的变量私有化,內存不会被销毁,并且使我们的变量很好的与外部作用域隔离,防止变量的全局污染,如果你用过JQuery可能会深有体会。

3.4 闭包的实际应用

3.4.1 块级作用域(立即执行函数)

这个例子很经典,说到会计作用域就会想到varlet,在ES6之前,我们做循环会遇到下面这种情况:

var fooArr = [];

for (var i = 0; i < 5; i ++) {
    fooArr[i] = function () {
        console.log(i);
    }
}

fooArr.forEach(fn => {
    fn();
})

结果我们发现打印了五次5

因此我们就可以借助闭包来解决这个问题。

var fooArr = [];

for (var i = 0; i < 5; i ++) {
    fooArr[i] = (function (i) {
        return function () {
            console.log(i);
        }
    })(i)
}

fooArr.forEach(fn => {
    fn();
})
// 0 1 2 3 4

此时得到了我们想要的结果啦!

3.4.2 变量私有

形成一个闭包后,我们可以使用闭包中的变量,下面这个案例留给大家去思考看一看会打印出什么东东。

function foo() {
    var a = 'foo';
    function zoo() {
        console.log(a);
    }
    
    return zoo;
}

function poo(fn) {
    var a = 'poo';
    fn();
}

poo(foo());

3.4.3 防抖和节流

// 节流
function throttle(fn, timeout) {
    let timer = null
    return function (...arg) {
        if(timer) return
        timer = setTimeout(() => {
            fn.apply(this, arg)
            timer = null
        }, timeout)
    }
}

// 防抖
function debounce(fn, timeout){
    let timer = null
    return function(...arg){
        clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(this, arg)
        }, timeout)
    }
}

3.4.4 函数柯里化

每次只输入一部分函数去处理,该函数会返回一个新函数,再将剩余参数输入到返回的新函数中,最终实现某个功能。

function add (a, b) {
    return a + b;
}
console.log(add(1+2)); // 3

如果写成柯里化是这个样子的:

function add (a) {
    return function (b) {
        return a + b;
    }
}

let currey = add(1);
console.log(currey(2)); // 3

// 或者也可以这样写
//console.log(add(2)(1)); // 3

3.5 使用闭包要注意什么

首先是使用闭包容易导致内存泄露,因为闭包会包含其他作用域的变量引用,因此闭包会占用更多的内存空间,所以闭包应该谨慎使用。

四、总结

希望通过阅读本文大家能够对JavaScript中的闭包有一个新的认识,能够在我们的工作中去灵活运用。也希望能够在面试中游刃有余,我是勇宝,一个前端小学生。

最近工作比较忙,网站有几天没有进行维护了,清明回来后会积极的去更新,欢迎大家评论区留言。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值