首页 > 其他分享 >JS-07 深入了解闭包

JS-07 深入了解闭包

时间:2024-05-30 11:30:15浏览次数:27  
标签:闭包 function 函数 作用域 JS var 变量 07

目录

1 变量作用域

2 作用域链

3 认识闭包

4 经典面试题

5 闭包的应用

6 闭包内存释放

7 闭包的优势


1 变量作用域

  1. 变量作用域的概念:就是一个变量可以使用的范围
  2. JS中首先有一个最外层的作用域:称之为全局作用域
  3. JS中还可以通过函数创建出一个独立的作用域(局部),其中函数可以嵌套,所以作用域也可以嵌套
    var gender="男";  //全局变量
    function fn(){
        console.log(age);    //因为age是在fn作用域内声明的
                            //age:undefined:既然有值就是可以访问

        console.log(height);//height不是在该作用域内部声明的,所以不能访问

        //-->2级作用域
        return function(){

            //-->3级作用域
            var height=180;
        }
        var age=5;
    }
    
    //注意:变量的声明和赋值是在两个不同时期的
    function fn(){
        console.log(age);   //undefined
        var age=18;
        console.log(age);   //18
    }

说明:看是否能被访问,看是否能获取到值

  • undefined:表示声明了但是未赋值,能输出这个值,说明是能访问到该变量,否则控制台直接报错
  • fn函数执行的时候,首先找到函数内部所有的变量、函数声明,把他们放在作用域中,给变量一个初始值:undefined    -->变量可以访问
  • 逐条执行代码,在执行代码的过程中,如果有赋值语句,对变量进行赋值(var声明变量)

2 作用域链

        问题引入:由于作用域是相对于变量而言的,而如果存在多级作用域,这个变量又来自于哪里?

        我们把这个变量的查找过程称之为变量的作用域链。简单来说,作用域链可以用以下几句话来概括:(或者说:确定一个变量来自于哪个作用域)

  1. 查看当前作用域,如果当前作用域声明了这个变量,就确定结果
  2. 查找当前作用域的上级作用域,也就是当前函数的上级函数,看看上级函数中有没有声明
  3. 再查找上级函数的上级函数,直到全局作用域为止
  4. 如果全局作用域中也没有,我们就认为这个变量未声明(xxx is not defined)
<script>
    function fn(callback){
        
        var age=18;
        callback()
    }
    
    fn(function(){
        console.log(age); //报错
        //分析:age变量:
        //1、查找当前作用域:并没有
        //2、查找上一级作用域:全局作用域
        //-->难点:看上一级作用域,不是看函数在哪里调用,而是看函数在哪里编写
        //-->因为这种特别,我们通常会把作用域说成是:词法作用域
    })
</script>

3 认识闭包

例:1

<script>
    function fn(){
        var a=5;
        return function(){
            a++;
            console.log(a); //a变量肯定是可以访问的
        }
    }

    var f1=fn(); //f1指向匿名函数
    f1();       //6
    f1();       //7
    f1();       //8

</script>

        代码执行到var f1=fn()行时,fn函数执行完毕,返回匿名函数

  • 一般认为函数执行完毕,变量就会释放,但是此时由于js引擎发现匿名函数要使用a变量,所以a变量并不能得到释放,而是把a变量放在匿名函数可以访问到的地方去了
  • a变量存在于f1函数可以访问到的地方,当然此时a变量只能被f1函数访问

例:2

    //返回值为 值
    function q1() {
        var a = {};
        return a;
    }

    var r1 = q1();
    var r2 = q1();
    console.log(r1 == r2);//false

    //返回值为 函数
    function q2() {
        var a = {}
        return function () {

            return a;
        }
    }
    var t3 = q2();//创建一个新的a对象,把a对象放在t3可以访问到的位置
    var o5 = t3();    //返回值a就是那个a

    var w3 = q2();//创建了一个新的a对象,把新的a对象放在w3可以访问到的位置
    var o8 = w3();//此时获取到的是一个新的a对象

    console.log(o5 == o8);    //false

    个人理解:声明多个变量指向外层函数,其多个变量互不干扰,相互独立;声明一个变量t1指向外层函数,一直用这个变量t1改变内层函数(闭包函数),如例1所示,会有所变化影响

4 经典面试题

    例:点击事件:当点击对应的div内容时,弹出对应的序号

  • 解决前:效果是点击任意一个div内容,都只会弹出 数字5
  • 解释:闭包问题,执行for结束后,i变成了数字5
  • 闭包问题产生原因:函数执行完毕后,作用域中保留了最新的i变量的值
<body>
    <div>111</div>
    <div>222</div>
    <div>333</div>
    <div>444</div>
    <div>555</div>
</body>
<script>
    //解决前
    // var divs=document.getElementsByTagName("div");

    // for (var i = 0; i < divs.length; i++) {
    //     const element = divs[i];
    //     element.onclick=function(){
    //         //i是来自于全局作用域
    //         alert(i)
    //     }
    // }

    //执行完for循环之后,i的值已经变成了5
    }
</script>

解决后:能实现问题描述


 //解决后
    var divs = document.getElementsByTagName("div");

    for (var i = 0; i < divs.length; i++) {
        const element = divs[i];
        //闭包的解决方案:为每个元素创建一个独立的闭包,闭包捕获了当前的索引i
        element.onclick = (function (currentIndex) {
            return function () {
                // currentIndex是捕获的当前循环索引的副本,它是通过闭包传递给内部函数的
                alert(currentIndex + 1)
            }
        })(i);

5 闭包的应用

  1. 模块化
  2. 防止变量被破坏

场景:在KTV里消费,规定只有消费满1000才能离开;未达到要求,则需要继续购物。

    //模块化思想:也是一种设计模式
    var ktv=(function KTV(){
        //为了保护leastPrice变量,将它放在函数内部
        var leastPrice=1000;

        var total=0;

        return {
            //购物
            buy:function(price){
                total+=price;
            },
            //结账
            pay:function(){
                if(total<leastPrice){
                    console.log('请继续购物');
                }else{
                    console.log('欢迎下次光临');
                }
            }
        }

    })()

   增加情景:来了一个老板的朋友要来唱K,不强制必须消费到1k,则说明老板需要能够修改最低消费金额,但是处于安全考虑,并不能让老板直接去修改leastPrice,或者说不能把leastPrice作为全局变量

修改return返回的内容:添加下面的函数

         editLeast:function(id,price){
                if(id===888){
                    leastPrice=price;
                    console.log("现在最低消费金额为:",leastPrice);
                }else{
                    console.log('权限不足');
                }
            }

6 闭包内存释放

<script>
    function f1() {
        var a = 5;
        return function () {
            a++;
            console.log(a);
        }
    }
    var q1 = f1();

    //要想释放q1里面保存的a,只能通过释放q1
    q1 = null;    //q1=undefined   //因为对象类型都是引用类型
</script>

7 闭包的优势

  1. 实现私有变量和数据封装
    • 闭包允许我们创建私有变量,这些变量只能在内部函数中访问和修改,外部无法直接访问。这提供了一种封装机制,可以隐藏数据的细节,从而提高代码的安全性。
    • 通过这种方式,我们可以将相关的数据和函数封装在一个闭包中,形成一个模块,这有助于减少全局变量的使用,防止全局变量污染。
  2. 保持数据的持久性
    • 闭包使得内部函数可以持续访问外部函数的变量,即使外部函数已经执行完毕。这对于需要保持数据状态或延长变量生命周期的场景非常有用。
    • 通过闭包,我们可以保存函数的内部状态,即使在函数执行结束后,状态仍然被保留。
  3. 创建函数工厂和动态函数
    • 闭包可以动态生成函数,每个函数都有自己的独立作用域和状态。这使得我们可以根据不同的参数生成不同的函数,实现函数工厂的模式。
  4. 逻辑连续性和避免额外逻辑
    • 当闭包作为另一个函数调用的参数时,可以避免脱离当前逻辑而单独编写额外逻辑。这有助于保持代码的清晰和逻辑的连续性。
  5. 方便调用上下文的局部变量
    • 闭包允许内部函数访问外部函数的变量,这使得我们可以更方便地调用和操作上下文中的局部变量。
  6. 加强封装性,达到对变量的保护作用
    • 通过将变量和函数封装在闭包中,我们可以增强代码的封装性,从而达到对变量的保护作用。这有助于防止外部代码对内部数据的非法访问和修改。

   然而,尽管闭包具有许多优势,但也需要注意其潜在的缺点,如内存消耗、性能问题、错误处理困难和对象状态共享等。因此,在使用闭包时需要根据具体的场景和需求来权衡其优缺点,并合理使用闭包以发挥其最大的优势。

标签:闭包,function,函数,作用域,JS,var,变量,07
From: https://blog.csdn.net/m0_68467925/article/details/139309996

相关文章

  • 如何初始化 FIrebase 云函数,以便使用凭据和 JSON 验证 Firebase Admin SDK 服务账户?
    我觉得我已经阅读了所有可用的资料,但我仍然无法理解这一点。我非常喜欢Google的产品,但有时其文档的简洁性令人头疼。我阅读了这个令人难以置信的雄辩答案,这个答案的作者和我一样毫无头绪,但他觉得有必要写一本循序渐进的儿童指南。不幸的是,他的回答过于针对他的项目,而不是我的项......
  • C#解析json的几种方式
    json格式的数据是javascript原生的一种数据格式,比xml更简洁。它有两种形式:json对象和json对象数组。 在此之前,有必要解释几个基本概念:json字符串,就是string,它一定是由双引号包起来的,如"{'name':'jerry'}"。这是一个string,尽管去掉双引号后它就是一个json对象。json对象,就是以......
  • Springboot报class path resource [xxxxx.json] cannot be resolved to URL because i
    当Springboot解析resources文件下的json文件时,在本地环境好用,部署到服务器上找不到文件内容报错classpathresource[xxxxx.json]cannotberesolvedtoURLbecauseitdosenotexist问题排查(1)pom.xml文件配置<build><resources><resource><d......
  • error in ./node_modules/@intlify/core-base/dist/core-base.cjs
    ERRORFailedtocompilewith1error......
  • JS取视频第一帧
    <!DOCTYPEhtml><htmllang="en"><head>  <metacharset="UTF-8">  <metaname="viewport"content="width=device-width,initial-scale=1.0">  <title>Document</title>&l......
  • C#中处理JSON数据的方式
    1.将对象序列化为JSON字符串在C#中,可以使用System.Text.Json和Newtonsoft.Json这两个流行的库来将对象序列化为JSON字符串。以下是使用这两个库进行序列化的示例代码:usingSystem;usingSystem.Text.Json;usingNewtonsoft.Json;publicclassPerson{publicstringN......
  • Java 中主要使用 Json 注释
    我从DB收到了JSON格式的数据,但我必须将所有字段对齐到适当的位置,如下所示:{"姓名":"Maveric"、"年龄":"26"、地址"波士顿街22号";}......
  • 【python007】读取csv文件url多进程下载图片数据(最近更新中)
    1.熟悉、梳理、总结项目研发实战中的Python开发日常使用中的问题、知识点等2.欢迎点赞、关注、批评、指正,互三走起来,小手动起来!3.欢迎点赞、关注、批评、指正,互三走起来,小手动起来!4.欢迎点赞、关注、批评、指正,互三走起来,小手动起来!......
  • daemon.json一些配置
    /etc/docker/daemon.json{"registry-mirrors":[  "https://registry.docker-cn.com",  "http://hub-mirror.c.163.com",  "https://docker.mirrors.ustc.edu.cn",  "https://si7y70hh.mirror.aliyuncs.com/"],......
  • 07-图4 哈利·波特的考试(浙大数据结构PTA习题)
    07-图4哈利·波特的考试        分数25        作者 陈越        单位 浙江大学哈利·波特要考试了,他需要你的帮助。这门课学的是用魔咒将一种动物变成另一种动物的本事。例如将猫变成老鼠的魔咒是haha,将老鼠变成鱼的魔咒是hehe等等。反方向变化......