• 什么是js的闭包,它是如何产生的


    1. 先说定义:

    当函数可以记住并访问所在的作用域时,就产生了闭包。即使函数是在当前作用域之外执行。

    2. 理解闭包需要知道的知识:作用域

    闭包产生的前置条件是作用域。JS的有两种作用域:全局作用域函数作用域,且作用域之间可以相互嵌套。就像下面这样:

    微信图片_20220610165142.png

    这里有三级作用域:全局作用域 - foo()函数的作用域 - bar()函数的作用域,分别对应 1 - 2 - 3

    3. 再放一段经典的产生闭包的代码:

    function foo() {
        var a = 2;
    
        function bar() {
            console.log(a);
        }
    
        return bar;
    }
    
    var baz = foo();
    
    baz(); //2 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4. 一个产生闭包的关键角色:垃圾回收器

    正常情况下foo()函数在执行结束以后,foo()整个的作用域都会被垃圾回收器销毁。但是var baz = foo()这里变量bazfoo()函数返回的bar()函数的引用,且bar()中还使用了foo()的变量,因此foo()函数的作用域会一直存在,且可以在foo()函数作用域之外还可以访问foo()的作用域。这样就产生了一个闭包。

    5. 闭包的一种使用场景

    先看一个比较经典的面试题:

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

    接触过这道题的人应该都知道这段代码执行结果是:1s后同时输出5个6。

    但是我们期望的结果肯定不是这样的,我们希望的结果是1s后输出1 2 3 4 5

    产生这种结果的原因是:

    1. 为什么是6: 使用var关键字是可以声明重复的变量,且for循环的{ .. }内又不属于一个与全局作用域区开来的作用域,因此当这个for循环结束以后,会在全局声明一个名称为i变量,它的值是6。
    2. 为什么都是6:因为setTimeout定时器属于事件循环的消息队列的内容,它并不会在JS主线程中同步执行,所以在for循环结束以后才会执行这5次setTimeout定时器,但是此时console.log(i)中的 i 此时访问的是全局的i变量,也就是固定的6。

    那么,如何通过闭包解决这个问题?

    有两种常用的解决方案。先放代码:

    // 最好解决方法:
    for (let i = 1; i <= 5; i++) {
    	setTimeout(() => {
    		console.log(i);
    	}, 1000)
    }
    
    // 用IIFE也可以实现
    for (var i = 1; i < 5; i++) {
    	(function (i) {
    	    	setTimeout(() => {
    	    		console.log(i);
    	    	}, 1000)
    	})(i)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这是如何解决问题的?

    第一种方法:

    第一种方法,直接将for循环中的var关键字改成了let关键字。

    let关键字声明变量有以下几个特点:

    1. let关键字可以将变量绑定到当前所在的作用域(通常是{ .. }内部)
    2. 不存在变量提升。即:使用let声明的变量的使用一定要在声明之后使用。
    3. 在块级作用域以外的地方无法访问声明的变量。
    4. 不允许使用let关键字多次声明同一个变量。

    在方法一中起作用的就是以上let声明变量四个特点中的1和3,

    • 改成let关键字,每次循环的i都被绑定在当前for循环的的块级作用域中,且这个i是无法被for循环以外的作用域访问到的。这样就形成一个封闭的作用域,for循环结束以后也就不会在全局作用域出现一个i变量。
    • 同时由于setTimeout定时器又引用了当前for循环的块级作用域的i,因此在每次循环结束以后,这个for循环的{ .. }的块级作用域并不会马上被垃圾回收器释放,此时就形成了一个闭包。在for循环结束以后就会形成5个闭包,且每个闭包都有一个i变量,每个i变量也都是正确的值。

    第二种方法:

    使用IIFE(立即执行函数),虽然for循环结束以后,会有一个全局的i = 6,但是执行定时器的时候访问的i却不是这个全局的i。

    在每次循环时,将当前的i通过参数的方式传递给IIFE中的匿名函数,每次for循环时,立即执行函数执行结束,但是其中的匿名函数的作用域由于setTimeout定时器还使用着匿名函数的形参,因此没有立即释放,这样也就形成了闭包。在JS主线程执行结束,执行到5个定时器任务时,因此也可以实现相同的功能。

    6. 闭包的其他使用场景

    本质上,如果将函数当作第一级的值类型并到处传递,就会看到闭包在这些函数中的应用。在定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers或者任何其它的异步任务中,只要使用了回调函数,实际上就是在使用闭包。

  • 相关阅读:
    C++学习第二十六天----内联函数与引用变量
    Oracle/PLSQL: LNNVL Function
    方舟生存进化下载服务器文件及搜服方法
    springboot web外部容器部署
    工具及方法 - 在线流程图描画
    Vue3简单项目流程分享——工作室主页
    软件过程与管理_期末复习知识点回顾总结
    抖音招聘直播报白:短视频流量红利和精准推送,让招聘更精准
    【Web UI自动化测试】Web UI自动化测试之日志收集篇(全网最全)
    Codeforces Round #833 (Div. 2) A-D.md
  • 原文地址:https://blog.csdn.net/vet_then_what/article/details/125515242