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

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

一、有趣的作用域链

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

js 复制代码
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. 全局下的变量只有当页面关闭的时候内存才会被释放。

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

    js 复制代码
    function foo () {
        var a = 2;
        console.log(a);
    }
    
    var f = foo(); // 2
    
    console.log(f); // undefined

2.2 内存释放(特殊情况)

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

js 复制代码
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 闭包是如何形成的

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

js 复制代码
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之前,我们做循环会遇到下面这种情况:

js 复制代码
var fooArr = [];

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

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

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

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

js 复制代码
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 变量私有

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

js 复制代码
function foo() {
    var a = 'foo';
    function zoo() {
        console.log(a);
    }
    
    return zoo;
}

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

poo(foo());

3.4.3 防抖和节流

js 复制代码
// 节流
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 函数柯里化

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

js 复制代码
function add (a, b) {
    return a + b;
}
console.log(add(1+2)); // 3

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

js 复制代码
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中的闭包有一个新的认识,能够在我们的工作中去灵活运用。也希望能够在面试中游刃有余,我是勇宝,一个前端小学生。

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

相关推荐
甜兒.26 分钟前
鸿蒙小技巧
前端·华为·typescript·harmonyos
她似晚风般温柔7893 小时前
Uniapp + Vue3 + Vite +Uview + Pinia 分商家实现购物车功能(最新附源码保姆级)
开发语言·javascript·uni-app
王中阳Go3 小时前
字节跳动的微服务独家面经
微服务·面试·golang
Jiaberrr4 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy4 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白4 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、4 小时前
Web Worker 简单使用
前端
web_learning_3214 小时前
信息收集常用指令
前端·搜索引擎
Ylucius4 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
tabzzz4 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack