前端百题斩【013】——用“闭包”问题征服面试官

写该系列文章的初衷是“让每位前端工程师掌握高频知识点,为工作助力”。这是前端百题斩的第13斩,希望朋友们关注公众号“执鸢者”,用知识武装自己的头脑。

13.1 定义

在JavaScript中,根据词法作用域的规则,内部函数总是可以访问其外部函数声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,就把这些变量的集合称为闭包。

13.2 闭包实现

在一个函数中嵌套另一个函数或者将一个匿名函数作为值传入另一个函数中。

// 函数fun1中嵌套了fun2,fun2作为参数返回,外部调用时仍能打印val1,构成闭包
function fun1() {
    const val1 = 10;
    function fun2() {
        console.log(val1);
    }

    return fun2;
}

function fun3() {
    const val2 = 20;
    // 定时器中的为一个匿名函数,其作为参数传入了,函数fun3执行完毕之后,1s钟后才会执行定时器函数,但此时还能打印val2,构成闭包
    setTimeout(function() {
        console.log(val2);
    }, 1000);
}

13.3 流程

根据下面的函数来看看闭包的整个执行流程

function main() {
    const val1 = 20;
    var val2 = 2
    function valResult() {
        return val1 * val2;
    }

    return valResult;
}

var result = main();
console.log(result()); // 40

上图中展示了各个时期的调用栈,需要重点关注以下几点:

  1. 当main函数执行完毕后,main函数的执行上下文从栈顶弹出;

  2. 返回的方法(valResult)中调用了main函数中的val1和val2变量,这两个变量就会打包成closure闭包,加到[[scopes]];

  3. 调用返回的方法时,作用域链为:result函数作用域——Closure(main)——全局作用域

13.4 优缺点

  1. 优点

    (1)可以重复使用变量,并且不会造成变量污染;

    (2)可以用来定义私有属性和私有方法

  2. 缺点

    (1)会产生不销毁的上下文,导致栈/堆内存消耗过大

    (2)会造成内存泄露。

扩展:闭包是怎么回收的?

  1. 如果闭包引入的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄露;

  2. 如果引用闭包的函数是一个局部变量,等函数销毁后,在下次JavaScript引擎执行垃圾回收时,判断闭包内容已经不再被使用,则js引擎的垃圾回收器就会进行回收。

13.5 用途

闭包用途主要有以下两个:

  1. 创建私有变量

function MyName(name) {
    return {
        getName() {
            return name;
        }
    }
}

const myName = MyName('lili');
// 只能通过getName访问对应的名字,别的方式访问不到
console.log(myName.getName()); // lili
  1. 作为回调函数。当把函数作为值传递到某处,并在某个时刻进行回调的时候就会创建一个闭包。例如定时器、DOM事件监听器、Ajax请求。

function fun(name) {
    setTimeout(() => {
        console.log(name);
    }, 1000);
}

fun('linlin');

13.6 经典闭包问题

多个子函数的[[scope]]都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。

for (var i = 1; i < 5; i++) {
    setTimeout(() => console.log(i), 1000);
}

上述代码本意是输出1、2、3、4,但结果却是四个5,为了解决该问题,主要有三种办法。

  1. 变量可以通过 函数参数的形式 传入,避免使用默认的[[scope]]向上查找

for (var i = 1; i < 5; i++) {
    (function(i) {
        setTimeout(() => console.log(i), 1000);
    })(i);
}
  1. 使用setTimeout包裹,通过第三个参数传入。(注:setTimeout后面可以有多个参数,从第三个参数开始其就作为回掉函数的附加参数)

for (var i = 1; i < 5; i++) {
    setTimeout(value => console.log(value), 1000, i);
}
  1. 使用 块级作用域,让变量成为自己上下文的属性,避免共享

for (let i = 1; i < 5; i++) {
    setTimeout(() => console.log(i), 1000);
}

1.如果觉得这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~

2.关注公众号执鸢者,领取学习资料,定期为你推送原创深度好文

3.扫描下方添加进群,里面大佬多多,一起向他们学习

1. 前端百题斩[001]——typeof和instanceof

2. 前端百题斩【002】——js中6种变量声明方式

3. 前端百题斩【003-004】——从基本类型、引用类型到包装对象

4. 前端百题斩【005】—— js中9种遍历对象的方法

5. 前端百题斩【006】——js中三类字符串转数字的方式

6. 前端百题斩【007】——js中必须知道的四种数据类型判断方法

7. 前端百题斩【008-009】——从JavaScript的代码执行过程到函数执行过程

8. 前端百题斩【010】——通俗易懂的JavaScript执行上下文

9. 六张图带你从HTTP/0.9进化到HTTP3.0

10. 理论与API相结合理解Node中的网络通信

11. 硬核知识点——浏览器中的三类五种请求

12. 理论与实践相结合彻底理解CORS

13. 三步法解析Express源码

14. 一篇搞定前端高频手撕算法题(36道)

15. 十七张图玩转Node进程——榨干它

16. 理论与API相结合理解Node中的网络通信

17. 一文彻底搞懂前端监控

18. 前端的葵花宝典——架构

19. canvas从入门到猪头

20. 前端工程师的一大神器——puppeteer

21. 2021 年前端宝典【超三百篇】

22. 前端也要懂机器学习(上)

23. 前端也要懂机器学习(下)

24. 学架构助力前端起飞

25. 假如只剩下canvas标签

26. Vue源码思想在工作中的应用

27. 一文搞定Diff算法

28. 百度、小红书三面,均遇“赛马”问题

29. 十五张图带你彻底搞懂从URL到页面展示发生的故事

30. 一文搞懂Cookie、Storage、IndexedDB

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值