循环中的闭包

news2025/1/27 13:09:47

目录

1. 什么是闭包?闭包的作用? 

1.1 可以访问 外部作用域 中变量的内部函数

1.2 闭包可以访问外部作用域中的变量及传参

2. 异步操作中 变量 的生命周期,取决于 闭包 的生命周期

2.1 Timer 定时器(保留到 定时器回调执行完毕、定时器被清空)

2.2 Event 事件处理函数(保留到 事件处理函数 被移除)

2.3 Ajax 请求数据(保留到 接收到接口返回数据,执行回调函数)

2.4 其他异步 API 使用闭包

3. 循环中的闭包

3.1 利用闭包(利用立即执行函数)

3.2 在 for 循环中使用 let 声明变量

3.3 利用 setTimeout 第三个参数

4. 闭包的垃圾回收机制

5. 推荐一篇很值得学习的面试题分析文章

5.1 文章

5.2 文章题目概述

5.2.1 基础题目 / 初步及格标准

5.2.2 如果期望代码的输出变成:5 -> 0,1,2,3,4,该怎么改造代码

5.2.3 如果期望代码的输出变成:0 -> 1 -> 2 -> 3 -> 4 -> 5,该怎么改造代码


1. 什么是闭包?闭包的作用? 

1.1 可以访问 外部作用域 中变量的内部函数

可以访问 外部作用域 中变量的内部函数(在 函数内部 或者 {} 块内部 定义一个函数,就创建了闭包)

举个栗子~~

// log()、simple() 是嵌套在 autorun() 函数里面的函数

(function autorun() {
    let x = 1;

    // 闭包示例
    function log() {
       // 在 log() 内部,可以访问到 外部作用域 中的变量 x,此时 log() 就是闭包
       console.log(x); 
    }

    // 纯函数示例
    function simple() {
       return 2 === Number(2);
    }

    log();
})();

关于内部函数,内部函数有两种(闭包、纯函数):

  • 闭包 —— 引用了外部作用域中变量的函数
  • 纯函数 —— 没有引用外部作用域中变量的函数,它们通常返回一个值,并且没有副作用

也可以这么理解,随着函数得执行完毕,某个变量因为被引用着(内部函数/闭包引用着)导致不能够被垃圾回收

1.2 闭包可以访问外部作用域中的变量及传参

即使 外部函数 / 外部块 已经执行完毕,内部函数仍然可以访问 外部函数 / 外部块 中定义的变量、接收的传参;让 局部变量 的值,始终保留在 内存 中

举个栗子~~

// 外部函数
(function autorun(p) {
    // 即使外部函数已经执行完毕
    let x = 1;
    setTimeout(function log() {
      // 内部函数仍然可以访问 外部函数 中定义的变量
      console.log(x); // 1
      // 内部函数仍然可以访问 外部函数 中J接收的参数
      console.log(p); // 10
    }, 10000);
})(10);

// 外部块 
{
    let x = 1;
    setTimeout(function log() {
      // 内部函数仍然可以访问 外部块 中定义的变量
      console.log(x);
    }, 10000);
}

 

闭包的外部作用域,在闭包定义的时候已决定,而不是执行的时候;闭包可以访问其外部(父)作用域中的定义的所有变量;

举个栗子~~

let x0 = 0;
(function autorun1() {
 let x1 = 1;
 (function autorun2() {
   let x2 = 2;
   (function autorun3() {
     let x3 = 3;
     // 闭包可以访问其外部(父)作用域中的定义的所有变量
     console.log(x0 + " " + x1 + " " + x2 + " " + x3); // 0 1 2 3
    })();
  })();
})();

2. 异步操作中 变量 的生命周期,取决于 闭包 的生命周期

当外部作用域执行完毕后,内部函数还存活(仍在其他地方被引用)时,闭包才真正发挥其作用

  • 在异步任务例如 timer 定时器,事件处理,Ajax 请求中被作为回调
  • 被外部函数作为返回结果返回,或者返回结果对象中引用该内部函数

2.1 Timer 定时器(保留到 定时器回调执行完毕、定时器被清空)

变量 x 将一直保留,直到以下两种情况出现:

  • 定时器的回调执行
  • clearTimeout() 被调用
(function autorun() {
    let x = 1;
    setTimeout(function log() {
      // 变量 x 将一直存活着直到定时器的回调执行或者 clearTimeout() 被调用
      // 如果用 setInterval() ,那么变量 x 将存活到 clearInterval() 被调用。
      console.log(x);
    }, 10000);
})();

 

2.2 Event 事件处理函数(保留到 事件处理函数 被移除)

变量 x 将一直保留,直到 事件处理函数 被移除

(function autorun() {
    let x = 1;
    // 当变量 x 在事件处理函数中被使用时,它将一直存活直到该事件处理函数被移除。
    $("#btn").on("click", function log() {
      console.log(x);
    });
})();

 

2.3 Ajax 请求数据(保留到 接收到接口返回数据,执行回调函数)

变量 x 将一直保留,直到 接收到接口返回数据,执行回调函数

(function autorun() {
    let x = 1;
    fetch("http://").then(function log() {
      // 变量 x 将一直存活到接收到后端返回结果,回调函数被执行。
      console.log(x);
    });
})();

 

2.4 其他异步 API 使用闭包

除了 Timer 定时器,Event 事件处理,Ajax 请求等常见的异步任务,还有其他的异步 API(比如 HTML5 Geolocation,WebSockets , requestAnimationFrame())

他们都使用到闭包的这一特性 ——  变量的生命周期取决于闭包的生命周期,被闭包引用的外部作用域中的变量,将一直存活直到闭包函数被销毁。

如果一个变量被多个闭包所引用,那么直到所有的闭包被垃圾回收后,该变量才会被销毁

3. 循环中的闭包

// 原始题目
for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i); // 1s 后,打印出 5,5,5,5,5
  }, 1000);
}

如何 将上述题目改成 1s 后,打印 0,1,2,3,4 呢?

3.1 利用闭包(利用立即执行函数)

立即执行函数,就是个闭包,它可以让局部变量的值,始终保持在内存中,对内部变量进行保护,外部访问不到

for (var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j);
    }, 1000);
  })(i);
}

 

3.2 在 for 循环中使用 let 声明变量

function initEvents() {
  for(let i = 1; i <= 3; i++){
    setTimeout(function timer() {
      console.log(i);
    }, 1000);
  }
}

 

3.3 利用 setTimeout 第三个参数

第三个参数将作为 setTimeout 第一个参数 fn 的参数

// 利用 setTimeout 的第三个参数,第三个参数将作为 setTimeout 第一个参数 fn 的参数
for (var i = 0; i < 5; i++) {
  setTimeout(function fn(i) {
    console.log(i);
  }, 1000, i); // 第三个参数i, 将作为 fn 的参数
}


// 将上述题目改成每间隔 1s 后,依次打印 0,1,2,3,4
for (var i = 0; i < 5; i++) {
  setTimeout(function fn(i) {
    console.log(i);
  }, 1000 * i, i);
}

 

4. 闭包的垃圾回收机制

在 Javascript 中,局部变量会随着 “函数的执行完毕” 而被销毁,除非还有指向他们的引用;

当闭包本身也被垃圾回收之后,闭包中的 私有状态 也会被垃圾回收;

不合理的使用闭包,会造成内存泄露(就是该内存空间使用完毕之后,未被回收)
 

[译]发现 JavaScript 中闭包的强大威力 - 掘金闭包是一个可以访问外部作用域的内部函数,即使这个外部作用域已经执行结束。 作用域决定这个变量的生命周期及其可见性。 当我们创建了一个函数或者 {} 块,就会生成一个新的作用域。需要注意的是,通过 var 创建的变量只有函数作用域,而通过 let 和 const 创建的变量既有函…https://juejin.cn/post/6844903769646317576

5. 推荐一篇很值得学习的面试题分析文章

5.1 文章

由浅入深,一步步讲解,受益很多,点赞👍

破解前端面试(80% 应聘者不及格系列):从闭包说起 - 掘金修订说明:发布《80% 应聘者都不及格的 JS 面试题》之后,全网阅读量超过 6W,在知乎、掘金、cnodejs 都引发了很多讨论,还被多个前端微信公号和技术媒体转载。酝酿许久之后,笔者准备接下来撰写前端面试题系列文章,内容涵盖 DOM、HTTP、浏览器、框架、编码、工程化等方…https://juejin.cn/post/6844903474212143117

5.2 文章题目概述

5.2.1 基础题目 / 初步及格标准

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(new Date, i);
    }, 1000);
}

console.log(new Date, i); // 5,5,5,5,5,5

用 箭头 表示其前后的两次输出之间有 1 秒的时间间隔,逗号 表示前后的两次输出之间的时间间隔可以忽略,则打印结果 —— 5 -> 5,5,5,5,5

如果上面的可以理解,就战胜了80%的人,及格了

 

5.2.2 如果期望代码的输出变成:5 -> 0,1,2,3,4,该怎么改造代码

法一 —— 立即执行函数

for (var i = 0; i < 5; i++) {
    (function(j) { // j = i
        setTimeout(function() {
            console.log(new Date, j);
        }, 1000);
    })(i);
}

console.log(new Date, i);

 

法二 —— setTimeout 第三个参数

for (var i = 0; i < 5; i++) {
    setTimeout(function(j) {
        console.log(new Date, j);
    }, 1000, i);
}

console.log(new Date, i);

法三 —— 利用 JavaScript 中基本类型(Primitive Type)的参数传递是按值传递

var output = function (i) {
    setTimeout(function() {
        console.log(new Date, i);
    }, 1000);
};

for (var i = 0; i < 5; i++) {
    output(i);  // 这里传过去的 i 值被复制了
}

console.log(new Date, i);

法四 —— 使用 es6 的 let

这个题目里不能用 let,因为会报错,最后一行执行时无法访问到 let

for (let i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(new Date, i);
    }, 1000);
}

// 下面这行报错,访问不到 i
console.log(new Date, i);

 

5.2.3 如果期望代码的输出变成:0 -> 1 -> 2 -> 3 -> 4 -> 5,该怎么改造代码

代码执行时,立即输出 0,之后每隔 1 秒依次输出 1,2,3,4,循环结束后在大概第 5 秒的时候输出 5(这里使用大概,因为 JS 中的定时器触发时机有可能是不确定的,具体可参见 How Javascript Timers Work)

比较简单粗暴的写法(木有加分效果):

  • 把最后一行的 console.log 设置定时器,放到最后执行;
  • 循环中的 console.log 定时器时间分别增加 1s
for (var i = 0; i < 5; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(new Date, j);
        }, 1000 * j);  // 这里修改 0~4 的定时器时间
    })(i);
}

setTimeout(function() { // 这里增加定时器,超时设置为 5 秒
    console.log(new Date, i);
}, 1000 * i);

如果把这次的需求抽象为:在系列异步操作完成(每次循环都产生了 1 个异步操作)之后,再做其他事情,代码该怎么组织?聪明的你是不是想起了什么? —— Promise 【ES6 中的新特性】

// 这里存放异步操作的 Promise
const tasks = [];

const output = (i) => new Promise((resolve) => {
    setTimeout(() => {
        console.log(new Date, i);
        resolve(); // 这里一定要 resolve,否则代码不会按预期 work
    }, 1000 * i); // 定时器的超时时间逐步增加
});

// 生成全部的异步操作
for (var i = 0; i < 5; i++) {
    tasks.push(output(i));
}

// 异步操作完成之后,输出最后的 i
Promise.all(tasks).then(() => {
    setTimeout(() => {
        console.log(new Date, i); // 注意这里只需要把超时设置为 1 秒
    }, 1000);
});

如何使用 ES7 中的 async/await 特性来让上面的代码变的更简洁?

// 模拟其他语言中的 sleep,实际上可以是任何异步操作
const sleep = (timeountMS) => new Promise((resolve) => {
    setTimeout(resolve, timeountMS);
});

// 声明即执行的 async 函数表达式
(async () => {
    for (var i = 0; i < 5; i++) {
        if (i > 0) {
            await sleep(1000);
        }
        console.log(new Date, i);
    }

    await sleep(1000);
    console.log(new Date, i);
})();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/39953.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里内部总结的微服务笔记,从入门到精通,初学者也能学的会

前言 随着互联网的发展&#xff0c;网站应用的规模也在不断的扩大&#xff0c;进而导致系统架构也在不断的进行变化。 一、系统架构演变 从互联网早起到现在&#xff0c;系统架构大体经历了下面几个过程: 单体应用架构--->垂直应用架构--->分布式架构--->SOA 架构-…

Flutter 单元测试例子

Flutter 单元测试例子 原文 https://medium.com/app-dev-community/flutter-unit-testing-with-simple-examples-9c07499e4079 前言 执行单元测试来验证软件的每个组件。因此&#xff0c;我们需要尽可能多地测试每个单独的微 widget 。这些都是由开发人员在开发阶段完成的。单元…

回归分析(2) 一元回归模型

如上所述&#xff0c;为了易于确定回归函数μ(x)中的未知参数&#xff0c;我们来讨论变量Y与x之间存在着线性相关关系的情形 散布在某一条直线的周围&#xff0e;于是&#xff0c;我们可以用线性回归方程 来描述Y与x之间的相关关系&#xff0c;并假设相应的误差&#xff08;称为…

Go-zero框架学习+xorm+gorm配置

Go-zero框架学习xormgorm配置 文章目录Go-zero框架学习xormgorm配置一、框架介绍二、go-zero快速搭建1.下载go-zero2.安装goctl3.项目初始化4.测试5.项目结构6.快速生成rpc服务7.快速添加api接口8.快速生成model服务8.快速生成Dockerfile9.快速生成K8s部署文件三.golang的ORM框…

[go学习笔记.第十七章.redis的使用] 1.redis的使用

1.redis基本介绍 (1).Redis 是 NoSQL 数据库&#xff0c;不是传统的关系型数据库,官网: https://redis.io/ 和http://redis.cn/ (2).Redis: REmote Dlctionary Sever&#xff08;远程字典服务器&#xff09;, Redis 性能非常高&#xff0c;单机能够达到 15w qps,通常适合做缓存…

刷爆力扣之盛最多水的容器

刷爆力扣之盛最多水的容器 HELLO&#xff0c;各位看官大大好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 今天阿呆继续记录下力扣刷题过程&#xff0c;收录在专栏算法中 &#x1f61c;&#x1f61c;&#x1f61c; 该专栏按照不同类别标签进行刷题&#xff…

安卓的分区一点有用知识:super、lpunpack、lpdump

我们知道这个安卓的镜像分区有很多个。 那么这个文章要介绍什么呢&#xff1f; 三个点&#xff1a; 一是现在的android支持动态分区&#xff0c;很多的东西都被放到super分区里面了&#xff0c;这个应该是可以配置的。然后super里面有比如system、vendor这种比较大的分区。那…

教务排课系统毕业设计,大学排课系统设计与实现,排课系统论文作品参考

功能清单 【后台管理员功能】 录入分院&#xff1a;录入分院名称&#xff0c;简介&#xff0c;详情 分院管理&#xff1a;管理已经录入分院&#xff0c;支持修改和删除 老师录入&#xff1a;录入老师姓名、联系方式、头像、老师简介 老师管理&#xff1a;管理所有已经录入老师…

基于Matlab模拟用于海况海洋学研究的 X 波段雷达系统(附源码)

目录 一、定义雷达系统参数 二、对海面进行建模 三、配置雷达收发器 四、生成数据多维数据集 五、处理海面回波 六、总结 七、程序 海事雷达系统在充满挑战的动态环境中运行。为了改进对感兴趣目标的检测并评估系统性能&#xff0c;必须了解海面返回的性质。 在本例中&a…

【操作系统】2.4 死锁

这一节也非常重要 2.4.1 死锁的概念 2.4.1 死锁的概念_StudyWinter的博客-CSDN博客 在并发环境下&#xff0c;各种进程因竞争资源而造成的一种互相等待对方手里的资源&#xff0c;导致各进程都阻塞&#xff0c;都无法向前推进的现象。这就是死锁&#xff0c;死锁发生后&#…

Jest API使用方法

如上面的知识图谱所示&#xff0c;一个常见的测试框架通常需要实现这些功能: ● before/after 钩子函数: 如beforeEach&#xff0c;afterEach&#xff0c; ● Mock方法&#xff1a; 函数Mock&#xff0c;时间mock等。 ● 断言: 判断一个描述是否正确&#xff0c;在Jest中常为 e…

你心心念念的RabbitMQ个人实践来了来了它来了

前言 MQ&#xff08;Message Queue&#xff09;就是消息队列&#xff0c;其有点有很多&#xff1a;解耦、异步、削峰等等&#xff0c;本文来聊一下RabbitMQ的一些概念以及使用。 RabbitMq 案例 Springboot整合RabbitMQ简单案例 基本概念 Exchange&#xff1a;消息交换机&a…

云原生系列 【基于CCE Kubernetes编排实战二】

✅作者简介&#xff1a; CSDN内容合伙人&#xff0c;全栈领域新星创作者&#xff0c;阿里云专家博主&#xff0c;阿里云问答板块版主&#xff0c;华为云享专家博主&#xff0c;掘金后端评审团成员 &#x1f495;前言&#xff1a; 最近云原生领域热火朝天&#xff0c;那么云原生…

Hystirx限流:信号量隔离和线程池隔离

背景&#xff1a; 最近工作中要处理服务高并发的问题&#xff0c;大流量场景下限流熔断降级可以说是必不可少的&#xff0c;打算对限流做一次改造&#xff0c;所以要先了解一下hytrix相关内容&#xff0c;比如了解一下线程池隔离和信号量隔离的区别。 **信号量&#xff1a;**信…

[网络工程师]-应用层协议-DHCP

BOOTP是最早的主机配置协议&#xff0c;动态主机配置协议&#xff08;Dynamic Host Configuration Protocol&#xff0c;DHCP&#xff09;则是在其基础上进行了改良的协议&#xff0c;是一种用于简化主机IP配置管理的IP管理标准。通过DHCP协议&#xff0c;DHCP服务器为DHCP客户…

集合学习笔记——Collection 全家桶

Collection是我们日常开发中使用频率非常高的集合&#xff0c;它的主要实现有List和Set,区别是List是有序的&#xff0c;元素可以重复;Set是无序的&#xff0c;元素不可以重复&#xff0c;我们简单看下继承关系&#xff1a; List的实现类主要线程不安全的ArrayList和LinkedList…

推挽输出和开漏输出-三极管-mos管

一、推挽输出 1.1推挽输出的概念 推挽&#xff08;push-pull&#xff09;输出是由两个MOS或者三极管组成&#xff0c;两个管子始终保持一个导通&#xff0c;另一个截止的状态。 图1 推挽电路示意图 当输入高电平时&#xff0c;叫做推&#xff1b; 上管Q1导通&#xff0c;下管…

【目标检测】Faster R-CNN论文的讲解

目录&#xff1a;Faster R-CNN论文的讲解一、前言二、回顾Fast R-CNN三、引入Faster R-CNN四、Faster R-CNN的介绍4.1 框架结构4.2 RPN如何产生候选区域的4.3 损失函数4.4 训练候选框提取网络4.5 RPN和Fast R-CNN共享特征的方法4.5.1 交替训练法4.5.2 近似联合训练法一、前言 …

C语言——学生信息管理系统

目录 功能展示 界面展示 所有功能模块&#xff1a; 功能1&#xff1a;菜单模块&#xff08;显示功能菜单&#xff09; 功能2&#xff1a;增加学生信息 功能3&#xff1a;输出学生信息&#xff08;查看所有学习信息&#xff09; 功能4&#xff1a;修改学生信息 功能5&a…

python3-GUI概述及应用

目录一、什么是GUI二、Python GUIPySimpleGUI概述一、PySimpleGUI简介二、PySimpleGUI特征三、输出设备hello,world猜数字一、玩家猜数字二、电脑猜数字21点游戏一、21点游戏简介二、程序代码一、什么是GUI 图形用户界面&#xff08;Graphical User Interface&#xff0c;简称…