V少JS基礎班之第六彈

一、 前言

第六彈內容是閉包。 距離上次函數的發布已經過去了一個多月, 最近事情比較多,很少有時間去寫文章, 低質量還得保證所以本章放草稿箱一個月了,終于補齊了,其實還有很多細節要展開說明,想著拖太久了,還是先發出來吧,后續慢慢補充。感謝各位佬的支持,希望多多點贊收藏,如果發現有什么不好的點也可以評論或私信我。
本系列為一周一更,計劃歷時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;為我們揭開了一種便捷的方法來應對這一挑戰。它不僅能夠將整個網站的數據轉化為適用…