一、 前言

第六弹内容是闭包。 距离上次函数的发布已经过去了一个多月, 最近事情比较多,很少有时间去写文章, 低质量还得保证所以本章放草稿箱一个月了,终于补齐了,其实还有很多细节要展开说明,想着拖太久了,还是先发出来吧,后续慢慢补充。感谢各位佬的支持,希望多多点赞收藏,如果发现有什么不好的点也可以评论或私信我。
本系列为一周一更,计划历时6个月左右。从JS最基础【变量与作用域】到【异步编程,密码学与混淆】。希望自己能坚持下来, 也希望给准备入行JS逆向的朋友一些帮助, 我现在脸皮厚度还行。先要点赞,评论和收藏。也是希望如果本专栏真的对大家有帮助可以点个赞,有建议或者疑惑可以在下方随时问。
先预告一下【V少JS基础班】的全部内容,我做了一些调整。看着很少,其实,正儿八经细分下来其实挺多的,第一个月的东西也一点不少。
第一个月【变量作用域BOMDOM数据类型操作符
第二个月【函数闭包、this、面向对象编程】
第三个月【原型链、异步编程、nodejs】
第四个月【密码学、各类加密函数】
第五个月【jsdom、vm2、express】
第六个月【基本请求库、前端知识对接】

==========================================================

二、本节涉及知识点

闭包

==========================================================

三、重点内容

一、概念

1- 闭包

概念:
首先我们看下闭包的权威解释(来自MDN)

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

翻译过来就是:
闭包是由捆绑起来(封闭的)的函数和函数周围状态(词法环境)的引用组合而成。换言之,闭包让函数能访问它的外部作用域。在 JavaScript 中,闭包会随着函数的创建而同时创建。

翻译:

英文中文解释
combination组合,结合结合体
bundled together / enclosed封装在一起函数和变量一起被打包
references引用指向外部变量的引用
surrounding state周围状态外层作用域中的变量
lexical environment词法环境在函数创建时所在的作用域链

提炼:
closure是闭包
闭包不是函数, 闭包是包含函数与函数所带的词法环境绑定的结合体

口语解释:
闭包是一个结构体。 他是一个函数和一个函数所携带的词法环境一起构成的结构体。

那我们现在就有一个问题了, 函数我们知道,词法环境是什么

2-词法环境&词法作用域

还是MDN:
A Lexical Environment is a specification type used to define the association of Identifiers (names) with specific variables and functions based on the lexical nesting structure of ECMAScript code.

a specification type: 规格类型
the association of:	关联
Identifiers:			标识符
specific variables:	特定变量
nesting structure of:	嵌套结构

提炼:
Lexical Environment 是引擎内部用来描述和追踪当前代码上下文中的变量、函数绑定信息的机制。

口语:
Lexical Environment 和 Lexical Scope 在代码调试过程中的可见性上可以默认是同一个东西。
但是 Lexical Environment, 他是引擎运行时创建的一个结构(对象)。而Lexical Scope则是代码层静态的结构。
在代码执行时,均被存放在上下文中。

好。 说了这么多概念。 我们可能还是不理解。 那我们直接上代码。 我们先从词法作用域【Lexical Scope】开始

【Lexical Scope】
作用域分为: 全局作用域,函数作用域,块作用域[ES6新增]

二、实例讲【作用域】

作用域其实在第一弹的时候就有讲过了。当时没有涉及到这么深,简单的说明了一下, 现在我们再次总结回顾一下:

作用域是在程序运行时代码中的某些特定部分中变量、函数和对象的可访问性。
作用域就是代码的执行环境,全局作用域就是全局执行环境,局部作用域就是函数的执行环境,它们都是栈内存
作用域又分为全局作用域和局部作用域。在ES6之前,局部作用域只包含了函数作用域,ES6的到来为我们提供了 ‘块级作用域’(由一对花括号包裹),可以通过新增命令letconst来实现;而对于全局作用域
在 Web 浏览器中,全局作用域被认为是 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的。
.在一个函数内部
2.在一个代码块(由一对花括号包裹)内部
let 声明的语法与 var 的语法一致。基本上可以用 let 来代替 var 进行变量声明,但会将变量的作用域限制在当前代码块中 (注意:块级作用域并不影响var声明的变量)

以上是对作用域的一个总结回顾。 具体细节我们再来讨论。

作用域分为: 全局作用域,函数作用域,块作用域[ES6新增]

1- 函数作用域:

指的是在 JavaScript 中,函数内部声明的变量和函数只能在该函数内部访问,外部无法直接访问。
也就是说,每当你声明一个函数,函数内部就创建了一个独立的作用域。

function foo() {let a = 10;console.log(a); // 10
}
foo();
console.log(a); // ReferenceError: a is not defined

变量a在函数foo内声明,函数外无法访问,会报错。

额外小知识:
作用域链: 当函数内访问变量时,会先查找自己作用域内有没有这个变量,如果没有,就去外层作用域找,直到找到全局作用域。

2- 块级作用域:
块级作用域就是被一对花括号 {} 包围起来的代码块,里面用 letconst 声明的变量,只在这对花括号内有效。
这意味着变量的生命周期和可访问范围严格限制在该代码块内。
3- 全局作用域(Global Scope):是 JavaScript 中最顶层的作用域,
任何在函数、块外部声明的变量和函数,都属于全局作用域。
这里就要说一下window对象了。 在ES6之前, window就等同于全局作用域。
但是在ES6之后, JavaScript引入了 letconst的概念。 
就导致,window其实只是全局作用域的一部分了。 
我们用以下这个图并配上代码去理解

当你运行 JS 时,JavaScript 引擎为每段代码创建一个执行上下文(Execution Context),它包含三个核心组件:

执行上下文(Global / Function)
├── VariableEnvironment(var/function)
├── LexicalEnvironment(let/const/class)
├── ThisBinding(this 绑定)

以下代码中的b,c在全局作用域中,但并未挂载到window上

var a = 1;
let b = 2;
const c = 3;console.log(window.a); // 1
console.log(window.b); // undefined
console.log(window.c); // undefined

原理图如下:

┌──────────────┐
│ GlobalExecutionContext │
│ ┌──────────────┐      │
│ │ VariableEnv  │ → window (global object)
│ │   a: 1       │      │
│ └──────────────┘      │
│ ┌──────────────┐      │
│ │ LexicalEnv   │ ← 你访问不到
│ │   b: 2       │
│ │   c: 3       │
│ └──────────────┘
└──────────────┘

好的,那我们接下来进入到了关键点了。

三、let和const 的引入与作用域链

我们先看下以下代码:

debugger;
var data = [];
for (var i = 0; i < 3; i++) {data[i] = function () {console.log(i);};
};
data[0]();
data[1]();
data[2]()输出的结果为:
3
3
3
为什么呢?我们可以看下流程。 代码执行就是顺序执行。
先用var声明了一个 data=[]
然后我们进入循环, 3次循环分别赋值
data[0] = function () {console.log(i);}
data[1] = function () {console.log(i);}
data[2] = function () {console.log(i);}
而此时的i是var声明的,所以挂载在window上。经过3次循环
window.i 的值就为3。 向下执行的时候。 
data[0]();
data[1]();
data[2]()开始调用函数, 将window.i传入。 返回的值就是3

那有没有办法去解决这个问题呢,我就想打印出0,1,2。 肯定是有的, 这就是let的作用。 我们只用将 var i = 0 换成 let i = 0

var data = [];
for (let i = 0; i < 3; i++) {data[i] = function () {console.log(i);};
};
data[0]();
data[1]();
data[2]()此时的输出结果就是:
0
1
2

为什么呢? 我们再看下流程
先用var声明了一个 data=[]
然后我们进入循环, 3次循环分别赋值

data[0] = function () {console.log(i);}
data[1] = function () {console.log(i);}
data[2] = function () {console.log(i);}

而此时的i是let声明的,所以他挂载在块级作用域上。 三次循环生成3个块级作用域,分别绑定在三个函数上。

data[0]();
data[1]();
data[2]()
向下执行的时候, 三个函数分别使用绑定的块级作用域中的i。 返回值就是012

总结:
这个案例我们了解到了。
第一: 作用域链的存在,代码在函数或者块级作用域中(局部作用域)执行的时候。会优先获取当前作用域中的变量,如果找不到,会向上层查找。
第二:let和const在块级作用域中声明的变量不会穿挂载到window上。 而var声明的变量可以挂在到window上。
第三:window 与 全局作用域不全等

四、闭包初始模样

到此,我们其实已经离闭包越来越近了。 甚至我们已经用到了闭包。

for (let i = 0; i < 3; i++) {data[i] = function () {console.log(i);};
};

以上代码,就是一个很标准的闭包结构。
我们一直都在背诵两个概念。
1- 函数内部嵌套另一个函数
2- 内函数返回外函数
满足这两个条件就是闭包。

其实这是完全错误的说法。 我们再次回顾一下开头我们从MDN中查找的概念:

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
翻译:
闭包是由捆绑起来(封闭的)的函数和函数周围状态(词法环境)的引用组合而成。换言之,闭包让函数能访问它的外部作用域。在 JavaScript 中,闭包会随着函数的创建而同时创建。

首先闭包是个结构,他不是一个函数。闭包是有函数和他组成的词法环境构成。
其次,闭包让函数能访问他的外部作用域

我们对照这个案例看一下:
我们有函数:
function () {console.log(i);};
也有作用域:
块级作用域中的i,被内层函数访问。
所以 function 函数 和 块级作用域 共同构建成了一个闭包结构。

五、我们熟悉的闭包

好的, 至此。我们已经完全理解了闭包。 那我们把块级作用域改写成函数作用域。
再来看一下我们一直在背诵的逻辑

var result = [];
var a = 3; 
function foo(a) {let total = 0; for (var i = 0; i < 3; i++) {result[i] = function () {total += i * a;console.log(total);}}
}
foo(1);
result[0]();
result[1]();
result[2]();

此时,我们把块级作用域跟换成了函数作用域。 虽然我们使用了var去声明了var i=0; 但是var一直在函数作用域内。
并且有内部的result[i]指向的函数使用。 此时就是我们最常熟知的闭包结构。

最后在这里, 我再次声明一下。 闭包是一个结构,他不是一个函数。 闭包是包含了 函数和它所关联的作用域,一起构成的一个结构
另外我们如果用outer函数来表示外部函数,inner函数表示内部函数。 我们的闭包应该是inner和他的作用域一起构成了一个闭包。

===========================================================

六、闭包的实际应用

ok,闭包的原理我们算是真正的理解了。
我们现在知道了原理,那我们要思考的应该是,我们为什么要用闭包呢?
其实在循环中使用let,已经跟我们说明了。 为什么我们要使用闭包。
当我们需要私有化变量的时候,我们就需要用到闭包。 我们不想var出来的变量直接穿透我们的作用域。
不想再我们的结构外层还有操作可以改动我们的变量时,就需要使用闭包了。
看以下代码:

const counter = (function () {let privateCounter = 0;function changeBy(val) {privateCounter += val;}return {increment() {changeBy(1);},decrement() {changeBy(-1);},value() {return privateCounter;},};
})();console.log(counter.value()); // 0counter.increment();
counter.increment();
console.log(counter.value()); // 2counter.decrement();
console.log(counter.value()); // 1

此时,在counter对象中。 privateCounter就是counter的私有变量。 在外面是无法改动函数内部privateCounter的值。
这就是闭包的应用

七、慎用闭包

其实对我们爬虫来说, 我们是不需要使用闭包的,我们要做到的是理解闭包的原理。 从而让我们更好的懂开发逻辑,懂逆向。
我们学习完闭包之后,可能就会觉得,闭包真是个好东西。我们应该多用闭包,甚至每次要私有化变量的时候都用一个外函数套内函数的方式好了。

但是过多的使用闭包会有一个问题, 每个闭包都有它的私有化变量。他们互相隔离,单独的占用一块内存。其实对性能的损耗是很大的。
所以在正常开发中,正确的处理思路是,把需要共享的变量绑定在原型链上,我们可以通过操作原型链来控制对应的变量。

这样就能在解决属性绑定的问题同时解决内存。那原型链我们还不清楚,我们下一章节就是原型链的讲解

最后的最后
本章就到这里。拖得太久,感谢各位佬的收看。如果觉得写得好,还请麻烦点赞收藏。确实是因为之前写文章大家都比较积极的点赞收藏给了我很大的动力。感谢各位大佬

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【面板数据】全国高频交易明细数据(2000-2024.7)

中国土地交易市场作为国家宏观调控的重要组成部分&#xff0c;其通过市场机制&#xff0c;使土地使用权在不同主体间流转&#xff0c;将土地资源配置给最具利用效率的部门或企业&#xff0c;提升土地利用率和经济产出。中国土地高频交易明细数据汇集了全国范围内2000-2024年7月…

MongoDB 常用增删改查方法及示例

MongoDB 的增删改查&#xff08;CRUD&#xff09;操作是其核心功能&#xff0c;主要通过 mongo shell 或驱动&#xff08;如 Node.js、Python 等&#xff09;实现。以下是最常用操作的详细说明及示例&#xff08;基于 mongo shell 语法&#xff09;。 ​一、插入操作&#xff…

moodle升级(4.5到5.0)

升级目标 由Moodle 4.5 (Build: 20241129) 升级到Moodle 5.0.1 (Build: 20250629) 参考教程&#xff1a;moodle升级&#xff08;详细版&#xff09;-CSDN博客 操作平台&#xff1a;宝塔 通过宝塔进行备份 备份文件 将/www/wwwroot/moodle 和/www/wwwroot/moodledata 复制…

基于Apache POI实现百度POI分类快速导入PostgreSQL数据库实战

## 引言:POI数据的价值与挑战 POI(Point of Interest)数据作为地理信息系统的核心要素,在智慧城市、位置服务、商业分析等领域具有重要价值。百度POI数据包含了丰富的地点信息(如名称、类别、坐标等),但如何高效处理这些数据并将其导入数据库进行分析是开发者面临的挑战…

linux LAMP 3

[rootcode apache2]# bin/apachectl AH00558: httpd: Could not reliably determine the server’s fully qualified domain name, using fe80::20c:29ff:fe2a:708a. Set the ‘ServerName’ directive globally to suppress this message root192.168.235.5s password:┌─…

UI自动化-Selenium WebDriver

前言 Selenium WebDriver 是 Selenium 项目中最核心、最强大的组件&#xff0c;它是一个用于自动化控制网页浏览器的开源 API&#xff08;应用程序编程接口&#xff09;。 简单来说&#xff0c;Selenium WebDriver 就是一个允许你用编程语言&#xff08;如 Java、Python、C#、…

具身多模态大模型在感知与交互方面的综述

引言在本学期方老师的《机器人与大模型》课上&#xff0c;我首次接触到了关于具身智能的前沿知识&#xff0c;尤其作为课上交互组的成员&#xff0c;从表情识别到语音交互到机械狗的开发实践进行了一些有意思的探索&#xff0c;使我在其中感受到了具身智能的巨大魅力和无限潜力…

UI 设计|审美积累 | 拟物化风格(Skeuomorphism)

拟物化是指把现实世界的材质、光影和结构带到数字界面中。木纹、金属、皮革、纸张等真实物体的质感&#xff0c;被细致地还原到屏幕上&#xff0c;让用户一眼就明白元素的意义与操作方式。它曾是iOS6之前移动端设计的主流风格&#xff0c;也一度被极简风格取代&#xff0c;但在…

EventBridge精准之道:CloudTrail事件 vs. 服务原生事件,我该如何选?

当我们深入使用AWS EventBridge时&#xff0c;常常会发现一个有趣的现象&#xff1a;对于同一个操作&#xff08;比如启动一个EC2实例&#xff09;&#xff0c;EventBridge中似乎会出现两种事件。一种来自CloudTrail&#xff0c;记录了API调用的行为&#xff1b;另一种则直接来…

【算法】动态规划 斐波那契类型: 740. 删除并获得点数

740. 删除并获得点数 中等 题目 给你一个整数数组 nums &#xff0c;你可以对它进行一些操作。 每次操作中&#xff0c;选择任意一个 nums[i] &#xff0c;删除它并获得 nums[i] 的点数。之后&#xff0c;你必须删除 所有 等于 nums[i] - 1 和 nums[i] 1 的元素。 开始你…

AWS MySQL 读写分离配置指南

# AWS JDBC Wrapper读写分离配置实战&#xff1a;Spring Boot MyBatis Plus完整解决方案 ## 前言 在微服务架构中&#xff0c;数据库读写分离是提升系统性能的重要手段。本文将详细介绍如何在Spring Boot项目中使用AWS JDBC Wrapper实现自动读写分离&#xff0c;重点解决MyBat…

opencv检测运动物体

检测到的所有移动物体中轮廓中找到面积最大的轮廓&#xff0c;并绘制这个轮廓的矩形框。 #include <opencv2/opencv.hpp> #include <iostream>int main() {// 打开视频文件或摄像头cv::VideoCapture capture;capture.open("move3.mp4"); // 打开视频文件…

Camera相机人脸识别系列专题分析之十五:人脸特征检测FFD算法之libcvface_api.so算法API详细注释解析

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: Camera相机人脸识别系列专题分析之十五:人脸特征检测FFD算法之libcvface_api.so算法API详细注释解析 目录 一、libcvface_api.so算法API详细注释解析

图像擦除论文-2:SmartEraser、Erase Diffusion、OmniEraser

图像生成模型应用系列——图像擦除&#xff1a; 图像擦除论文-1&#xff1a;PixelHacker、PowerPanint等 图像擦除论文-2&#xff1a;擦除类型数据集构建(1) Erase Diffusion Erase Diffusion: Empowering Object Removal Through Calibrating Diffusion Pathways https://git…

九识无人车陕西运营中心展厅启幕 打造智能城配物流新标杆

7月1日&#xff0c;九识无人车陕西运营中心展厅正式开业&#xff0c;全国业务版图再添重要一子。这座展厅是九识在陕西省的首家展厅&#xff0c;由九识第一位正式提车的客户、首位代理商伙伴孙朋奇先生打造。展厅集产品展示与技术体验于一体&#xff0c;成为西北地区城配领域自…

AI智能体|扣子(Coze)搭建【沉浸式历史故事解说视频】工作流

主包讲解历史对我们的好处&#xff0c;纯个人观点&#xff01; 这个世界是存在一些规律的&#xff0c;很多东西并不能够通过自己的聪明去创新&#xff0c;去改变的。 无论你怎么样创新&#xff0c;你都会回到哪个规律中去&#xff0c;比如很多人做一些商业模式的创新&#xff0…

Softhub软件下载站实战开发(十):实现图片视频上传下载接口

文章目录 Softhub软件下载站实战开发&#xff08;十&#xff09;&#xff1a;实现图片视频上传下载接口 &#x1f5bc;️&#x1f3a5;系统架构图核心功能设计 &#x1f6e0;️1. 文件上传流程2. 关键技术实现2.1 雪花算法2.2 文件校验机制 ✅2.3 文件去重机制 &#x1f50d;2.…

[JS逆向] 喜马拉雅登录案例 -- 补环境

博客配套代码发布于github&#xff1a;喜马拉雅登录 &#xff08;欢迎顺手Star一下⭐&#xff09; 相关知识点&#xff1a;webpack 补环境 相关爬虫专栏&#xff1a;JS逆向爬虫实战 爬虫知识点合集 爬虫实战案例 逆向知识点合集 此案例目标为逆向成功对应的参数&#xff0c…

大语言模型推理系统综述

摘要 近年来&#xff0c;随着 ChatGPT 等服务推动大语言模型&#xff08;LLM&#xff09;的快速普及&#xff0c;一批专门面向 LLM 推理的系统相继涌现&#xff0c;如 vLLM、SGLang、Mooncake 和 DeepFlow。这些系统设计工作的核心动因是 LLM 请求处理过程中所特有的自回归特性…

用Firecrawl轻松获取网站数据,提升AI应用的效率!

&#x1f525; Firecrawl&#xff1a;助力AI应用的强大工具&#xff01; 在数字化信息爆炸的时代&#xff0c;如何高效地从海量网页中提取有用数据变得尤其重要。Firecrawl的问世&#xff0c;为我们揭开了一种便捷的方法来应对这一挑战。它不仅能够将整个网站的数据转化为适用…