【閉包】前端的“保護神”——閉包詳解+底層原理

目錄

?一、閉包是什么?概念

二、閉包為什么存在?作用

1. 創建私有變量

2. 實現數據封裝與信息隱藏

3. 模擬私有方法

4. 保存函數執行時的狀態

5. 回調函數和事件處理

6. 模塊化編程

7. 懶加載與延遲執行

?三、閉包怎么用?實踐+業務場景

1. 封裝私有變量

2. 延遲執行(定時器、異步回調)

3. 事件監聽和回調函數

5. 防抖和節流

業務場景:權限控制和角色管理

四、深入底層了解閉包的運行原理(難度指數????)


?一、閉包是什么?概念


閉包是指 函數可以“記住”并訪問定義時的作用域,即使這個函數在外部被調用時,依然能訪問到其定義時的父函數的局部變量。

  • 父函數和子函數

    • 閉包通常發生在一個函數(父函數)內部定義了另一個函數(子函數),且子函數可以訪問父函數的局部變量。
  • 通過return暴露子函數

    • 當父函數返回子函數時,子函數就形成了閉包。因為子函數不僅僅是返回的函數,它還“記住”了父函數的作用域。
  • 作用域鏈和內存管理

    • 通常,父函數的局部變量在父函數執行完畢后會被銷毀,但由于閉包的存在,這些局部變量會被保留在內存中,直到閉包不再被引用。
    • 閉包使得父函數的局部變量不被銷毀,同時也避免了全局作用域的污染,因為它們只在閉包內部可見。


二、閉包為什么存在?作用

1. 創建私有變量

閉包最常見的作用之一是實現 私有變量。在 JavaScript 中,變量通常是公開的,任何函數都能訪問它們。而閉包允許我們創建只能通過特定函數訪問的私有變量,這樣就可以避免外部代碼隨意訪問或修改它們。

  • 示例:
function createCounter() {let count = 0; // 這是一個私有變量return {increment: function() {count++;console.log(count);},decrement: function() {count--;console.log(count);},getCount: function() {return count;}};
}const counter = createCounter();
counter.increment(); // 輸出: 1
counter.increment(); // 輸出: 2
console.log(counter.getCount()); // 輸出: 2
// count 變量是私有的,外部無法直接訪問

在這個例子中,count 變量通過閉包被封裝在 createCounter 函數中,外部無法直接訪問和修改它,只有通過 incrementdecrement getCount 方法才能操作它。

2. 實現數據封裝與信息隱藏

閉包提供了數據封裝的能力,可以將狀態和行為封裝在一個函數內部,并通過暴露的接口與外部進行交互。這有助于信息隱藏,防止外部代碼不小心或惡意地修改內部數據。

  • 示例:

function bankAccount(initialBalance) {let balance = initialBalance; // 私有變量return {deposit: function(amount) {balance += amount;console.log(`Deposited: $${amount}`);},withdraw: function(amount) {if (balance >= amount) {balance -= amount;console.log(`Withdrew: $${amount}`);} else {console.log('Insufficient funds');}},getBalance: function() {return balance;}};
}const myAccount = bankAccount(1000);
myAccount.deposit(500); // Deposited: $500
myAccount.withdraw(200); // Withdrew: $200
console.log(myAccount.getBalance()); // 1300
// 不能直接訪問或修改 balance

這里的 balance 變量在 bankAccount 函數的作用域內被封裝,外部無法直接訪問或修改它,只有通過 depositwithdraw getBalance 方法才能與其交互。

3. 模擬私有方法

除了私有變量,閉包也可以用來模擬 私有方法。你可以將某些功能封裝在閉包內部,外部只能通過公開的方法調用它們,從而達到隱藏細節、減少外部依賴的目的。

  • 示例:

function car(model) {let speed = 0; // 私有變量function accelerate() {speed += 10;console.log(`Accelerating... Speed is now ${speed} km/h`);}return {start: function() {console.log(`${model} is starting`);accelerate();}};
}const myCar = car('Toyota');
myCar.start(); // Toyota is starting// Accelerating... Speed is now 10 km/h

在這個例子中,accelerate 函數是私有的,外部無法直接調用它,只有通過 start 方法間接調用。

4. 保存函數執行時的狀態

閉包能夠保持其外部函數的執行上下文,即使外部函數已經執行完畢。這樣,我們可以保存函數的 狀態,在后續的調用中繼續使用這些狀態。這對于處理 異步操作回調函數 中的狀態非常有用。

  • 示例:
function makeAdder(x) {return function(y) {return x + y; // 閉包可以記住 x 的值};
}const add5 = makeAdder(5);
console.log(add5(10)); // 15
const add10 = makeAdder(10);
console.log(add10(10)); // 20

在這個例子中,makeAdder 返回的函數是一個閉包,它“記住”了 x 的值。即使 makeAdder 執行結束后,x 仍然在閉包中保存,并且在后續的調用中可以使用它。

5. 回調函數和事件處理

在前端開發中,閉包廣泛應用于 事件處理異步回調。它們能夠保持對外部數據(如事件觸發時的狀態、函數參數等)的訪問,即使在異步操作完成后,閉包仍然能夠訪問這些數據。

  • 示例:事件處理中的閉包
function setupButton() {let counter = 0; // 閉包中的私有狀態document.getElementById('myButton').addEventListener('click', function() {counter++;console.log(`Button clicked ${counter} times`);});
}setupButton();

在這個例子中,事件回調函數可以訪問 counter 變量,它即使在 setupButton 函數執行完畢后仍然保持狀態。

6. 模塊化編程

閉包幫助我們將代碼分成獨立的模塊,每個模塊有自己的私有數據和方法。這樣不僅可以避免全局命名沖突,還可以提高代碼的可維護性和可復用性。

  • 示例:
const counterModule = (function() {let count = 0; // 私有變量return {increment: function() {count++;console.log(count);},decrement: function() {count--;console.log(count);}};
})();counterModule.increment(); // 1
counterModule.decrement(); // 0

通過立即執行函數表達式(IIFE),counterModule 模塊中的 count 是私有的,外部無法直接訪問。閉包保證了每個模塊都有獨立的作用域和私有數據。

7. 懶加載與延遲執行

閉包還可以用于延遲執行函數和延遲計算,常見于懶加載場景。例如,某些數據或資源的加載操作可以通過閉包延遲到需要時再執行。

  • 示例:

function fetchData() {let data = null;return function() {if (data === null) {console.log('Fetching data...');data = 'Some data'; // 模擬數據加載}return data;};
}const getData = fetchData();
console.log(getData()); // Fetching data... Some data
console.log(getData()); // Some data

這里,data 只在第一次調用 getData() 時被加載,之后就不會再進行加載操作,閉包保存了 data 的狀態。

?三、閉包怎么用?實踐+業務場景

1. 封裝私有變量

閉包常常用于封裝私有變量和創建數據的封裝(即模塊化編程)。在 JavaScript 中,通常沒有內建的私有變量機制,但閉包可以幫助你達到類似的效果。

  • 示例:計數器
function createCounter() {let count = 0;  // 私有變量return {increment: function() {count++;console.log(count);},decrement: function() {count--;console.log(count);},getCount: function() {return count;}};
}const counter = createCounter();
counter.increment();  // 輸出: 1
counter.increment();  // 輸出: 2
counter.decrement();  // 輸出: 1
console.log(counter.getCount());  // 輸出: 1

解析

  • count 是一個私有變量,只能通過 incrementdecrement getCount 方法訪問。
  • 外部無法直接訪問 count,實現了數據的封裝。

2. 延遲執行(定時器、異步回調)

閉包經常用于處理異步操作和定時任務。例如,使用 setTimeout setInterval 時,閉包允許你保留函數的執行上下文,從而延遲執行某些操作。

  • 示例:延遲執行任務

function createDelayedTask(message, delay) {return function() {setTimeout(function() {console.log(message);}, delay);};
}const delayedTask = createDelayedTask('Hello, World!', 2000);
delayedTask();  // 2秒后輸出: Hello, World!

解析

  • createDelayedTask 返回一個閉包,這個閉包可以記住其外部環境中的變量(如 message delay)。
  • 通過 setTimeout 延遲輸出 message,即使函數 createDelayedTask 已經執行完畢。

3. 事件監聽和回調函數

閉包在事件監聽器和回調函數中非常常見。它可以讓回調函數訪問外部作用域中的變量,從而保持對數據的引用。

5. 防抖和節流

防抖和節流是常見的性能優化技巧。防抖(Debouncing)通常用于限制某些操作頻繁觸發(如輸入框中的搜索建議),而節流(Throttling)則是控制某些操作的觸發頻率(如窗口大小調整事件)。

業務場景:權限控制和角色管理

閉包可以用于權限管理和角色管理的場景中,通過閉包來封裝不同角色的權限信息,從而提供靈活的權限控制。

  • 示例:權限管理
function createRoleChecker(role) {const permissions = {admin: ['read', 'write', 'delete'],user: ['read'],guest: []};return function(permission) {if (permissions[role] && permissions[role].includes(permission)) {console.log(`${role} has ${permission} permission.`);} else {console.log(`${role} does not have ${permission} permission.`);}};
}const adminChecker = createRoleChecker('admin');
const userChecker = createRoleChecker('user');
const guestChecker = createRoleChecker('guest');adminChecker('write');  // admin has write permission.
userChecker('write');   // user does not have write permission.
guestChecker('read');   // guest does not have read permission.

解釋

  • createRoleChecker 返回一個閉包,它保存了角色的權限信息。
  • 每次調用 roleChecker 時,可以判斷特定角色是否擁有某個權限。
  • 通過閉包,你可以靈活地管理角色和權限數據,避免權限數據暴露。

四、深入底層了解閉包的運行原理(難度指數????)

思考:

  • 下面代碼輸出什么?
  • A(2) 執行時,局部變量 x y 存儲在內存中,它們什么時候會被銷毀
function A(y) {let x = 2;function B(z) {console.log(x + y + z);}return B;
}let C = A(2);
C(3);

上述執行的過程中到底在做什么:

  1. A(2) 的調用:

    • JavaScript 引擎會為 A(2) 創建一個 執行上下文。
    • 當調用 A(2) 時,y 賦值為 2,并且在 A 內部創建了一個局部變量 x = 2 和一個函數 B(z)
    • 然后,A 返回了函數 B
  2. 形成閉包:

    • 函數 BA 內部定義,因此它形成了閉包,能夠訪問 A 內部的變量 xy,即使 A 執行完畢,B 仍然可以訪問這些變量。
    • A(2) 執行完,JavaScript 會銷毀 A 的執行上下文,但由于 B 是通過閉包持有對 A 作用域的引用,因此 x y 并沒有被銷毀,它們的內存空間會保留下來。
  3. B 賦值給 C:

    • 通過 let C = A(2);,變量 C 被賦值為函數 B,且 C 具有 A 中的作用域(閉包),能夠訪問 xy
  4. 調用 C(3):

    • 當調用 C(3) 時,實際執行的是 B(3)。JavaScript 會創建C(3) 的執行上下文:即 B(3) 的執行上下文。
    • B 中,x y 來自 A 的作用域,z 來自 B 的參數。因此,x + y + z 被計算為 7,并打印出來。
    • C(3) 調用結束,C(3) 的執行上下文 B(3) 的執行上下文會被銷毀。?但閉包仍然存在,因為 B 被保存在變量 C 中,并且 C 仍然引用著閉包。當 C B 被垃圾回收時,閉包才會被銷毀。因此,在 C(3) 執行后,雖然 B 的執行上下文棧幀被銷毀,但閉包中的內存(如 x y)會繼續存在,直到 C 不再引用 B

參考【易混概念】執行上下文和內存空間的聯系區別

留作業:如果閉包 B 被賦值給多個其他變量,這些變量會如何影響 x y 的內存空間

評論區做答。

訂閱《前端通過之路》,助你一路通關!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/88699.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/88699.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/88699.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

算法學習筆記:19.牛頓迭代法——從原理到實戰,涵蓋 LeetCode 與考研 408 例題

牛頓迭代法(Newtons Method)是一種強大的數值計算方法,由英國數學家艾薩克?牛頓提出。它通過不斷迭代逼近方程的根,具有收斂速度快、適用范圍廣的特點,在科學計算、工程模擬、計算機圖形學等領域有著廣泛應用。牛頓迭…

小白學Python,操作文件和文件夾

目錄 前言 一、操作文件路徑 1.獲取當前路徑 2.創建文件夾 (1)mkdir()函數 (2)makedirs() 函數 3.拼接路徑 4.跳轉路徑 5.判斷相對路徑和絕對路徑 6.獲取文件路徑和文件名 二、操作文件和文件夾 1.查詢文件大小 2.刪除…

015_引用功能與信息溯源

引用功能與信息溯源 目錄 引用功能概述支持的模型引用類型API使用方法引用格式應用場景最佳實踐 引用功能概述 什么是引用功能 Claude的引用功能允許在回答基于文檔的問題時提供詳細的信息來源引用,幫助用戶追蹤和驗證信息的準確性。這個功能特別適用于需要高可…

ROS2中的QoS(Quality of Service)詳解

ROS2中的QoS(Quality of Service)詳解1. 主要QoS參數2. 為什么需要設置QoS3. QoS兼容性規則4. 選擇QoS策略的建議5. 調試QoS問題的方法6. 踩坑:訂閱話題沒有輸出可能的原因:調試方法QoS是ROS2中用于控制通信質量和行為的機制。它定…

Cursor三大核心AI功能

一:Tab鍵:智能小助手 1.1 單行/多行代碼補全 在代碼中寫出要實現的功能,第一次按Tab生成代碼,第二次按Tab接受代碼。1.2 智能代碼重寫 對已有代碼重新編寫。 寫個注釋告訴AI重構方法,然后鼠標點到方法內部,…

cesium添加原生MVT矢量瓦片方案

項目中需要基于cesium接入mvt格式的服務并支持屬性拾取查詢,通過一系列預研測試,最后選擇cesium-mvt-imagery-provider開源插件完成,關鍵源碼信息如下: npm i cesium cesium-mvt-imagery-provider //安裝依賴包// 加載圖層import…

AI金融風控:識別欺詐,量化風險的新利器

AI金融風控:識別欺詐,量化風險的新利器深度學習算法穿透海量交易數據,92.5%的不良貸款識別率宣告了金融風險防控新時代的來臨。深圳桑達銀絡科技有限公司在2025年6月申請的“基于人工智能的金融交易反欺詐系統”專利,揭示了金融風…

【unitrix】 5.0 第二套類型級二進制數基本結構體(types2.rs)

一、源碼 這是一個使用 Rust 類型系統實現類型級(type-level)二進制數的設計。 //! 類型級二進制數表示方案(第二套方案) //! //! 使用嵌套泛型結構體表示二進制數,支持整數和小數表示。use crate::sealed::Sealed;/// 類型級二進制數結構體 …

DAY01:【ML 第一彈】機器學習概述

一、三大概念 1.1 人工智能(AI) Artificial Intelligence 人工智能AI is the field that studies the synthesis and analysis of computational agents that act intelligently 1.2 機器學習(ML) Machine Learning 機器學習Fi…

AGX Xavier 搭建360環視教程【一、先確認方案】

設備默認自帶 NVIDIA 硬件編解碼能力(NVDEC/NVENC),但是需要你在 OpenCV 和 FFmpeg 里正確啟用 調通 GStreamer 或 nvmpi,才真正能用起來!這里的硬解碼是核心:Jetson 平臺的硬解碼,要么走 GStr…

服務器怎么跑Python項目?

在服務器上運行 Python 項目通常涉及 環境配置、依賴安裝、項目部署 和 進程管理。以下是詳細步驟:1. 連接服務器確保你能通過 SSH 訪問服務器:ssh usernameyour_server_ip(如果是本地測試,可跳過這一步)2. 安裝 Pytho…

【軟件設計師】

UML 類圖中的關系用例圖中的關系 關系例子類圖用例圖順序圖 概念示例通信圖活動圖泳道圖狀態圖

Java 內部類詳解:從基礎到實戰,掌握嵌套類、匿名類與局部類的使用技巧

作為一名 Java 開發工程師,你一定在實際開發中遇到過這樣的場景:想在一個類內部定義另一個邏輯相關的類;需要為某個接口或抽象類提供一個臨時實現(比如監聽器);想利用面向對象特性來組織代碼結構&#xff0…

Java設計模式之行為型模式(觀察者模式)介紹與說明

一、模式結構 觀察者模式包含以下四個角色: Subject(主題/被觀察者) 維護觀察者列表,提供注冊(registerObserver)、移除(removeObserver)觀察者的方法,并定義通知所有觀察…

實現一個點擊輸入框可以彈出的數字軟鍵盤控件 qt 5.12

我們將創建兩個自定義組件: 1. NumericInputField:一個輸入框,當點擊時彈出數字鍵盤。 2. NumericKeyboard:一個可縮放的數字鍵盤。 設計思路: - NumericInputField 是一個常規的輸入框,但點擊后會彈出 Num…

Java 深入解析:JVM對象創建與內存機制全景圖

第一章:引言 Java 是一種面向對象的編程語言,對象(Object)是其最基本的組成單位。Java 的“一切皆對象”不僅體現在語法層面,更體現在運行時,幾乎所有數據都以對象形式存在于內存中。 然而,很…

Redis 基本操作筆記

1. Redis 簡介 Redis(Remote Dictionary Server)是一個開源的、高性能的鍵值對存儲系統,通常作為數據庫、緩存、消息中間件等使用。它支持多種數據類型,包括字符串、哈希、列表、集合、有序集合等。 Redis 特點: 性能&…

Docker從環境配置到應用上云的極簡路徑

Docker從環境配置到應用上云的極簡路徑主要包括環境配置、應用容器化、選擇云平臺及部署應用等步驟,具體如下: - 配置Docker環境: - 安裝Docker:根據操作系統下載對應版本的Docker安裝包。如在Linux系統中,可使用命令…

Slicer渲染Dicom到nrrd

Slicer渲染Dicom到nrrd 工作中遇到一些處理Dicom數據的需求,個人通過網絡上的一些教程 對于原始數據嘗試轉換到nrrd時,發現部分的窗體數據的渲染方向不一致 進一步發現這些很多定義的方向是跟設備廠家強相關的,不同廠家對于同一段的Dicom參…

QT中設計qss字體樣式但是沒有用【已解決】

檢查一下stylesheet里面是不是有不能被QT讀取的CSS語言,可能會跟字體顏色沖突錯誤示范:/* 錯誤示例:QSS 中使用 box-shadow */ QPushButton {box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); /* Qt 不支持此屬性 */ }刪掉就行了如果后續想用陰影…