当前位置: 首页 > news >正文

【JavaScript】一文了解JS的闭包

🍈作者简介:大家好,我是亦世凡华、渴望知识储备自己的一名在校大学生

🍇个人主页:亦世凡华、的博客

🍓系列专栏:JavaScript专栏

🥝推荐一款模拟面试刷题神器🔥:点击跳转进入网

目录

🙉初识闭包

🍇什么是闭包

🍈如何产生闭包

🍉产生闭包条件

🍊闭包的作用

🍋闭包的生命周期

🍌闭包的应用

🍍闭包的缺点及解决方法

🥭闭包案例


🙉初识闭包

闭包可谓是JS的一大难点也是面试中常见的问题之一,今天开始梳理一下闭包的知识,请诸君品鉴。

🍇什么是闭包

闭包是嵌套的内部函数内部函数包含被引用变量(函数)的对象。闭包存在于嵌套的内部函数中,例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来。当然如何直观的查看闭包可以通过chrome来查看,这里有个坑需要反馈一下,新版的chrome需要先调用fun2()才允许debugger,这样才能显示闭包。

<script>
    function fn1(){
        var a = 2;
        function fn2(){//执行函数定义就会产生闭包(不用调用内部函数)
            console.log(a);
        }
        //新版的chrome需要返回一下内部函数才会显示闭包
        return fn2()
    }
    fn1()
</script>

🍈如何产生闭包

当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包

<script>
    // 将函数作为另一个函数的返回值
    function fn1(){
        var a = 2;
        function fn2(){//
            a++;
            console.log(a);
        }
        return fn2 //将一个内部函数作为一个外部函数的返回值返回
    }
    var f = fn1()
    //整个过程产生了一个闭包,主要看你产生了几个内部函数对象,调用了几次外部函数
    //闭包的特点就是函数内部的变量会一直存在于内存中,不会立即释放。
    f()//3   这里的f()是调用了内部函数
    f()//4
</script>

<script>
    // 将函数作为实参传递给另一个函数调用
    function showDelay(msg,time){
        //setTimeout 的第一个参数是函数,符合闭包的规则
        setTimeout(function(){
            alert(msg)
        },time)
    }
    showDelay('张三',2000)
</script>

🍉产生闭包条件

函数嵌套内部函数引用了外部函数的数据(变量/函数)

🍊闭包的作用

使用函数内部的变量在函数执行完毕后,仍然存活在内存中(延长了局部变量的生命周期);让函数外部可以操作(读写)到函数内部的数据(变量/函数)。

🍋闭包的生命周期

产生:在嵌套的内部函数定义执行完时就产生了(不是在调用),死亡:在嵌套的内部函数称为垃圾对象时就死亡了。

<script>
    function fn1 () {
        //此时闭包就已经产生了(函数提升,内部函数对象已经创建了)
        var a = 2;
        function fn2 () {//
            a++;
            console.log(a);
        }
        return fn2 //将一个内部函数作为一个外部函数的返回值返回
    }
    var f = fn1()
    f()//3   
    f()//4
    f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
</script>

🍌闭包的应用

定义JS模块(具有特定功能的js文件),将所有的数据和功能都封装在一个函数的内部(私有的),只向外暴露一个包含n个方法的对象和函数;模块的使用者只需要通过模块暴露的对象调用方法来实现对应的功能。

//myModule.js 文件
function myModule(){
    // 私有数据
    var msg = 'My Module'
    function showUpper(){
        console.log('showUpper' +msg.toUpperCase());
    }
    function showLow(){
        console.log('showLow' +msg.toLowerCase());
    }
    //向外暴露对象(给外部使用的方法)
    return {
        showUpper:showUpper,
        showLow:showLow
    }
}

//index.html文件
<script src="./myModule.js"></script>
<script>
    var module = myModule()
    module.showUpper()
    module.showLow()
</script>

我们也可以通过匿名函数来实现闭包,这样能很便捷的调用闭包里面的属性,虽然会达到我们想要的效果,但是可能会造成全局的变量名污染,建议使用第一种。

//myModule2.js文件
(function(){
    // 私有数据
    var msg = 'My Module'
    // 操作数据的函数
    function showUpper(){
        console.log('showUpper' +msg.toUpperCase());
    }
    function showLow(){
        console.log('showLow' +msg.toLowerCase());
    }
    //向外暴露对象(给外部使用的方法)
    window.myModule2 = {
        showUpper:showUpper,
        showLow:showLow
    }
})()

//index.js文件
<script src="./myModule2.js"></script>
<script>
    myModule2.showUpper()
    myModule2.showLow()
</script

🍍闭包的缺点及解决方法

在我们使用闭包过程中,函数执行完后,函数内部的局部变量没有释放,占用内存时间会变长,容易造成内存泄漏,所以在日常开发中,尽量避免闭包的出现,或者要对局部变量及时释放。

<script>
    function fn1(){
        var arr = new Array[100000]
        function fn2(){
            console.log(arr.length);
        }
        return fn2
    }
    var f = fn1()
    f()
    //不用闭包或者回收闭包
    f = null//让内部函数成为垃圾对象 --> 回收闭包
</script>

内存溢出:一种程序运行出现的错误,当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误。

<script>
    var obj = {}
    for(var i=0;i<10000;i++){
        obj[i]=new Array(1000000)
        console.log('------');
    }
</script>

内存泄漏:占用的内存没有及时释放,内存泄漏积累多了就容易导致内存溢出。常见的内存泄漏:意外的全局变量、没有及时清理的计时器或回调函数、闭包。

<script>
    //意外的全局变量
    function fn(){
        a = 10;
        console.log(a);
    }
    // 调用函数虽然能打印a,但是a并没有被释放掉。一不注意就设置了一个全局变量
    fn()

    //没有及时清理计时器或回调函数
    var intervalId = setInterval(function(){ //启动循环定时器后不清理
        console.log('--------');
    },2000)
    // clearInterval(intervalId)

    //闭包
    function fn1(){
        var a = 2 //闭包 a 并没有被释放掉
        function fn2(){
            console.log(++a)
        }
        return fn2
    }
    var f = fn1()
    f()
    // f = null 不执行这条语句,a的值一直在
</script>

🥭闭包案例

<script>
    // 案例一:
    var name = "this is Window"
    var object = {
        name:"this is Object",
        getName:function(){
            return function(){
                return this.name
            }
        }
    }
    //闭包的this只能是全局,若在当前作用域中定义了this,就直接使用定义的this,若没定义,则需要一层层向外找,直到全局为止
    //本题是没有闭包的
    alert(object.getName()())//this is Window

    // 案例二:
    var name1 = "this is Window"
    var object1 = {
        name1:"this is Object",
        getName:function(){
            //定义的that形成了闭包,内部函数引用了外部函数的变量,而this指向的是object,所以返回的是object中的name1
            var that = this;
            return function(){
                return that.name1
            }
        }
    }
    alert(object1.getName()())// this is Object
</script>
<script>
    //没有使用闭包的话,数据是没有保留的,所以n传递给o之后,下次运算o值还是上次的值不会发生改变
    function fun(n,o){
        console.log(o);
        return{
            fun:function(m){
                return fun(m,n)
            }
        }
    }
    //在执行fun(0)之后,n被之前的n=0,一直被调用
    var a = fun(0); //闭包里面的n传入了0
    a.fun(1); 
    a.fun(2);
    a.fun(3)//undefined,0,0,0
     
    //链式执行会导致n的改变,n是前面函数执行的形参
    var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2

    //c.fun(2)、c.fun(3)都调用了fun(1)留下的闭包n
    var c = fun(0).fun(1); 
    c.fun(2); 
    c.fun(3)//undefined,0,1,1
</script>

🍃JavaScript的学习还是要以多练习为主,想要练习JavaScript的朋友,推荐可以去牛客网看一看,链接:牛客网 里面的IT题库内容很丰富,属于国内做的很好的了,最重要的是里面的资源是免费的,是课程+刷题+面经+求职+讨论区分享,一站式求职学习网站,感兴趣的可以去看看。

相关文章:

  • 2022前端vue面试题
  • docker相关试题
  • python 之名称空间与作用域
  • python之面向过程编程思想与匿名函数及其应用
  • docker 之Dockerfile
  • python之logging 模块(简洁版)
  • Docker 搭建 Redis Cluster 集群环境
  • Docker 容器编排利器 Docker Compose
  • Docker Swarm 集群环境搭建及弹性服务部署
  • Docker Compose 搭建 Redis Cluster 集群环境
  • 计算机初识
  • 计算机硬件五大单元
  • 计算机硬件组成详解
  • 计算机硬盘接口及操作系统
  • 计算机核心概念之进程、线程、进程池、进程三态、同步、异步、并发、并行、串行...
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • 【刷算法】从上往下打印二叉树
  • es6(二):字符串的扩展
  • gf框架之分页模块(五) - 自定义分页
  • Laravel 实践之路: 数据库迁移与数据填充
  • MySQL几个简单SQL的优化
  • Netty 4.1 源代码学习:线程模型
  • Object.assign方法不能实现深复制
  • vue自定义指令实现v-tap插件
  • WePY 在小程序性能调优上做出的探究
  • 道格拉斯-普克 抽稀算法 附javascript实现
  • 动手做个聊天室,前端工程师百无聊赖的人生
  • 使用SAX解析XML
  • 数据可视化之 Sankey 桑基图的实现
  • 译米田引理
  • k8s使用glusterfs实现动态持久化存储
  • ​VRRP 虚拟路由冗余协议(华为)
  • ​低代码平台的核心价值与优势
  • ​如何防止网络攻击?
  • ###C语言程序设计-----C语言学习(3)#
  • #LLM入门|Prompt#2.3_对查询任务进行分类|意图分析_Classification
  • (libusb) usb口自动刷新
  • (分布式缓存)Redis持久化
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (三)Pytorch快速搭建卷积神经网络模型实现手写数字识别(代码+详细注解)
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (四)c52学习之旅-流水LED灯
  • (一)使用Mybatis实现在student数据库中插入一个学生信息
  • (转)es进行聚合操作时提示Fielddata is disabled on text fields by default
  • (转载)从 Java 代码到 Java 堆
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .NET MVC第五章、模型绑定获取表单数据
  • .NET 中的轻量级线程安全
  • .NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换
  • .net下简单快捷的数值高低位切换
  • .考试倒计时43天!来提分啦!
  • @Transactional类内部访问失效原因详解
  • @vue/cli 3.x+引入jQuery
  • [2017][note]基于空间交叉相位调制的两个连续波在few layer铋Bi中的全光switch——
  • [2023年]-hadoop面试真题(一)