《作用域大冒險:從閉包到內存泄漏的終極探索》

?“愛自有天意,天有道自不會讓有情人分離”


大家好,關于閉包問題其實實際上是js作用域的問題,那么js有幾種作用域呢?

作用域類型關鍵字/場景作用域范圍示例
全局作用域var(無聲明)整個程序var x = 10;
函數作用域var?在函數內函數內部function foo() { var x; }
塊級作用域letconst{}?代碼塊內if (true) { let x; }
模塊作用域ES6 模塊單個模塊文件export const x = 1;
詞法作用域函數定義時定義時的外層作用域鏈閉包

我們常見的就是?全局作用域,函數作用域和塊級作用域了。

閉包叫做詞法作用域,我沒聽說過這個詞,總而言之,閉包是一個作用域問題

?什么是閉包??

閉包(Closure)是 JavaScript 中的一個核心概念,它指的是 ??函數能夠記住并訪問其定義時的作用域(詞法環境),即使該函數在其作用域之外執行??。

用人話來講就是:閉包是可以訪問到另一個函數作用域中變量的函數?

在循環嵌套的函數結構中,閉包就很容易理解了。內部函數可以訪問到外部函數中的變量,但是外部函數不能訪問到內部函數中的變量。

我來舉一個例子:
?


function outerFunction(outerParam) {// 外部函數的變量let outerVar = "我是外部變量";const outerConst = "我是外部常量";function innerFunction(innerParam) {// 內部函數的變量let innerVar = "我是內部變量";// 內部函數可以訪問:// 1. 自己的變量console.log("內部函數訪問自己的變量:", innerVar);console.log("內部函數訪問自己的參數:", innerParam);// 2. 外部函數的變量和參數console.log("內部函數訪問外部變量:", outerVar);console.log("內部函數訪問外部常量:", outerConst);console.log("內部函數訪問外部參數:", outerParam);return innerVar;}console.log("\n----- 分割線 -----\n");// 外部函數嘗試訪問內部函數的變量(會失敗)console.log("外部函數可以訪問自己的變量:", outerVar);console.log("外部函數可以訪問自己的參數:", outerParam);// 下面這行如果取消注釋會報錯// console.log("外部函數無法訪問內部變量:", innerVar); // ReferenceError: innerVar is not defined// 調用內部函數const result = innerFunction("內部參數");console.log("只能通過內部函數的返回值來獲取內部變量:", result);return innerFunction;
}// 測試
const innerFn = outerFunction("外部參數");
console.log("\n----- 分割線 -----\n");
innerFn("新的內部參數");

輸出結果:

----- 分割線 -----外部函數可以訪問自己的變量: 我是外部變量
外部函數可以訪問自己的參數: 外部參數
內部函數訪問自己的變量: 我是內部變量
內部函數訪問自己的參數: 內部參數
內部函數訪問外部變量: 我是外部變量
內部函數訪問外部常量: 我是外部常量
內部函數訪問外部參數: 外部參數
只能通過內部函數的返回值來獲取內部變量: 我是內部變量----- 分割線 -----內部函數訪問自己的變量: 我是內部變量
內部函數訪問自己的參數: 新的內部參數
內部函數訪問外部變量: 我是外部變量
內部函數訪問外部常量: 我是外部常量
內部函數訪問外部參數: 外部參數

?這個代碼展示的是:

  1. 內部函數可以訪問:

    • 自己的變量(innerVar)和參數(innerParam)
    • 外部函數的變量(outerVar)、常量(outerConst)和參數(outerParam)
  2. 外部函數只能訪問:

    • 自己的變量(outerVar)和參數(outerParam)
    • 無法直接訪問內部函數的變量(innerVar)
    • 只能通過內部函數的返回值來間接獲取內部變量的值

這就是所謂的"作用域鏈",內部函數可以向上訪問外部作用域的變量,但外部作用域不能訪問內部作用域的變量

閉包能干什么?

閉包能干的事情有:變量私有化回調函數函數柯里化。

變量私有化

什么是變量私有化?

變量私有化是一種編程技術,目的是??限制變量的訪問范圍??,使其只能在特定的作用域或模塊內被訪問和修改,外部代碼無法直接操作。這樣可以提高代碼的安全性、可維護性,并減少命名沖突的風險。

通過閉包實現一下變量私有化

我們來做一個計數器案例,外部不能修改count,只能通過?increment()?和?getCount()?操作。

function createCounter() {let count = 0; // 私有變量,外部無法直接訪問return {increment() {count++;},getCount() {return count;},};
}const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 1
console.log(count); // 報錯:count is not defined(無法直接訪問私有變量)

我們利用閉包創建了一個私有變量count,無法在外部訪問,只有通過我們的increment()?和?getCount()?操作才能操作和訪問。

回調函數

回調函數想必就不用介紹了,在任何語言中都有出現和應用。

閉包可以讓回調函數記住并訪問其定義時的作用域變量,即使回調在異步操作(如?setTimeoutfetch、事件監聽)中被調用。

介紹一個例子:

setTimeout?回調??

??問題??:直接使用循環變量?i?會導致所有回調輸出相同的值(var?沒有塊級作用域)。
??解決??:用閉包保存每次循環的?i?值。

// ? 錯誤寫法(輸出 3 個 3)
for (var i = 0; i < 3; i++) {setTimeout(function() {console.log(i); // 輸出 3, 3, 3}, 100);
}// ? 正確寫法(閉包保存 i 的值)
for (var i = 0; i < 3; i++) {(function(j) { // 立即執行函數(IIFE)創建閉包setTimeout(function() {console.log(j); // 輸出 0, 1, 2}, 100);})(i); // 傳入當前 i 的值
}

監聽事件中的閉包

function setupButtons() {const buttons = document.querySelectorAll('button');for (var i = 0; i < buttons.length; i++) {(function(index) { // 閉包保存當前按鈕的索引let count = 0; // 每個按鈕獨立的計數器buttons[index].addEventListener('click', function() {count++;console.log(`按鈕 ${index} 被點擊了 ${count} 次`);});})(i);}
}setupButtons();

我們發現在回調函數場景中閉包的作用很多是幫我們留下或者說是記住作用域變量,可以讓我們的邏輯更加簡單。

函數柯里化

wow,好高級的詞!

什么是函數柯里化

函數柯里化??(Currying)是一種將 ??多參數函數?? 轉換為 ??一系列單參數函數?? 的技術。
它的核心思想是:??每次只接受一個參數,并返回一個新函數,直到所有參數收集完畢,才執行最終計算??。

總而言之就是:分布傳參。

剛才我們在回調函數中了解到:“我們發現在回調函數場景中閉包的作用很多是幫我們留下或者說是記住作用域變量,可以讓我們的邏輯更加簡單。”

那么:函數柯里化是指將一個多參數函數轉換為一系列單參數函數的過程。那么閉包剛好利用它能記住函數定義時的作用域這一特點就可以實現柯里化;

用閉包做函數柯里化

簡單例子:

// 普通函數(3個參數)
function sum(a, b, c) {return a + b + c;
}// 手動柯里化(閉包實現)
function curriedSum(a) {return function(b) {return function(c) {return a + b + c;};};
}// 調用方式
console.log(curriedSum(1)(2)(3)); // 6

閉包帶來的危害

1. 內存泄漏(Memory Leaks)??

??問題描述??

閉包會長期持有外部函數的變量,阻止垃圾回收(GC),導致內存無法釋放。

??示例??

function createHeavyObject() {const bigData = new Array(1000000).fill("X"); // 占用大量內存的變量return function() {console.log(bigData.length); // 閉包引用 bigData,即使外部函數執行完畢};
}const holdClosure = createHeavyObject(); // bigData 無法被回收!

??解決方法??

  • 在不需要閉包時手動解除引用:
    holdClosure = null; // 釋放閉包持有的內存
  • 避免在閉包中保存不必要的變量(如 DOM 元素、大對象)。

??2. 性能損耗(Performance Overhead)??

??問題描述??

  • 閉包會創建額外的作用域鏈,訪問外部變量比訪問局部變量稍慢。
  • 在頻繁調用的函數(如動畫、滾動事件)中使用閉包可能導致性能下降。

??示例??

// 每次觸發 scroll 都會訪問閉包變量
window.addEventListener("scroll", function() {const cached = heavyCompute(); // 閉包可能持有 heavyCompute 的結果console.log(cached);
});

??解決方法??

  • 對于高頻操作,盡量使用局部變量而非閉包變量。
  • 用?debounce/throttle?限制觸發頻率。

??3. 意外的變量共享(Unexpected Shared State)??

??問題描述??

循環中創建的閉包可能共享同一個變量(尤其是用?var?時)。

??示例??

// ? 錯誤寫法:所有按鈕都輸出 3
for (var i = 0; i < 3; i++) {setTimeout(function() {console.log(i); // 輸出 3, 3, 3(i 是共享的)}, 100);
}// ? 正確寫法:用 IIFE 或 let 隔離變量
for (let i = 0; i < 3; i++) {setTimeout(function() {console.log(i); // 輸出 0, 1, 2}, 100);
}

??解決方法??

  • 使用?let/const?替代?var(塊級作用域)。
  • 用 IIFE(立即執行函數)隔離變量:
    for (var i = 0; i < 3; i++) {(function(j) {setTimeout(function() {console.log(j); // 正確輸出 0, 1, 2}, 100);})(i);
    }

??4. 調試困難(Debugging Challenges)??

??問題描述??

閉包的作用域鏈可能讓變量來源難以追蹤,增加調試復雜度。

??示例??

function outer() {const secret = 42;return function inner() {debugger; // 在這里查看作用域鏈,可能有多層閉包console.log(secret);};
}
const mystery = outer();
mystery();

??解決方法??

  • 在 Chrome DevTools 中使用 ??Scope?? 面板查看閉包變量。
  • 避免過度嵌套閉包,保持函數簡潔。

??5. 閉包與?this?的混淆??

??問題描述??

閉包中的?this?可能丟失預期指向(尤其是嵌套函數中)。

??示例??

const obj = {name: "Alice",greet: function() {return function() {console.log(this.name); // ? 輸出 undefined(this 指向全局或 undefined)};}
};
obj.greet()(); // 調用內部函數

??解決方法??

  • 使用箭頭函數(繼承外層?this):
    greet: function() {return () => console.log(this.name); // ? 正確輸出 "Alice"
    }
  • 提前綁定?this
    greet: function() {const self = this;return function() {console.log(self.name); // ? 正確輸出 "Alice"};
    }

?

閉包是一把雙刃劍,它既可以:創建私有變量,避免全局變量污染?也會:閉包會導致內存泄漏,如果不銷毀閉包,他引用的外部變量就會一直保存在內存當中,無法被釋放,從而導致內存泄漏 。

就像她對你一樣,既能在戀愛中讓你開心幸福,也會在吵架時讓你痛苦不堪

但是,只要我們珍惜這些幸福,勇敢面對好好處理這些痛苦就能讓我們的感情歷久彌新。閉包也是一樣啊,只要我們利用好它的優點,規避全局變量污染就能讓我們變成大佬。

所以,面對再多的困難,再多的誤會也要拉緊她的手,會幸福的!?

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

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

相關文章

為什么Makefile中的clean需要.PHONY

原因一&#xff1a;避免Makefile檢查時間戳 前置知識&#xff1a;makefile在依賴文件沒有改變時不會執行編譯命令 #第一次執行&#xff0c;OK [rootVM-16-14-centos ~]# make g -E main.cc -o main.i g -S main.i -o main.s g -c main.s -o main.o g main.o -o main#第二…

垂直行業突圍:工業軟件在汽車、航空領域的 “破壁” 實踐

在當今科技高速發展的時代&#xff0c;工業軟件已悄然完成從通用工具到垂直行業 “戰略武器” 的蛻變。特別是在汽車與航空這兩大高端制造領域&#xff0c;工業軟件的價值早已超越單純的效率提升&#xff0c;成為關乎核心技術自主可控的關鍵要素&#xff0c;一場圍繞工業軟件的…

07.Python代碼NumPy-排序sort,argsort,lexsort

07.Python代碼NumPy-排序sort&#xff0c;argsort&#xff0c;lexsort 提示&#xff1a;幫幫志會陸續更新非常多的IT技術知識&#xff0c;希望分享的內容對您有用。本章分享的是NumPy的使用語法。前后每一小節的內容是存在的有&#xff1a;學習and理解的關聯性&#xff0c;希望…

LVDS系列8:Xilinx 7系可編程輸入延遲(一)

在解析LVDS信號時&#xff0c;十分重要的一環就是LVDS輸入信號線在經過PCB輸入到FPGA中后&#xff0c;本來該嚴格對齊的信號線會出現時延&#xff0c;所以需要在FPGA內部對其進行延時對齊后再進行解析。 Xilinx 7系器件中用于輸入信號延時的組件為IDELAYE2可編程原語&#xff0…

AI驅動研發效率在中后臺的實踐

本文探討了AI驅動的中后臺前端研發實踐&#xff0c; 涵蓋設計出碼、接口定義轉換、代碼擬合、自動化測試等多個環節&#xff0c;通過具體案例展示了AI技術如何優化研發流程并提升效率。特別是在UI代碼編寫和接口聯調階段&#xff0c;并提出了設計出碼&#xff08;Design to Cod…

【Rust 精進之路之第6篇-流程之舞】控制流:`if/else`, `loop`, `while`, `for` 與模式匹配初窺

系列: Rust 精進之路:構建可靠、高效軟件的底層邏輯 作者: 碼覺客 發布日期: 2025-04-20 引言:讓代碼“活”起來——指令的流動 在前面的文章中,我們已經掌握了 Rust 的基礎數據類型(標量和復合類型)以及如何通過變量綁定來存儲和命名它們。這相當于我們準備好了程序…

C++ 表達式求值的基礎(四十九)

1. 運算符的分類 1.1 按操作數個數 一元運算符&#xff08;Unary&#xff09; 作用于單個操作數&#xff1a; 取地址 &obj解引用 *ptr邏輯非 !b一元加減 x, -x遞增遞減 i, i-- 二元運算符&#xff08;Binary&#xff09; 作用于兩個操作數&#xff1a; 算術運算 a b, a …

Three.js + React 實戰系列 : 從零搭建 3D 個人主頁

可能你對tailiwindcss毫不了解&#xff0c;別緊張&#xff0c;記住我們只是在學習&#xff0c;學習的是作者的思想和技巧&#xff0c;并不是某一行代碼。 在之前的幾篇文章中&#xff0c;我們已經熟悉了 Three.js 的基本用法&#xff0c;并通過 react-three-fiber 快速構建了一…

Kotlin實現Android應用保活方案

Kotlin實現Android應用保活優化方案 以下的Android應用保活實現方案&#xff0c;更加符合現代Android開發規范&#xff0c;同時平衡系統限制和用戶體驗。 1. 前臺服務方案 class OptimizedForegroundService : Service() {private val notificationId 1private val channel…

windows拷貝文件腳本

1、新建腳本文件xxx.bat&#xff0c;名字任意&#xff0c;后綴未.bat即可&#xff0c;將以下內容拷貝進去&#xff0c;修改src和des為自己文件的目錄即可。 echo off :: 設置字符集為UTF-8&#xff0c;命令窗口能正確顯示中文字符。 chcp 65001 rem 讀取當前目錄并進入當前目…

Qt 核心庫總結

Qt 核心庫&#xff08;QtCore&#xff09; QtCore 是 Qt 框架的基礎模塊&#xff0c;提供非圖形界面的核心功能&#xff0c;是所有 Qt 應用程序的基石。它包含事件循環、信號與槽、線程管理、文件操作、字符串處理等功能&#xff0c;適用于 GUI 和非 GUI 應用程序。本文將從入…

大模型相關面試問題原理及舉例

大模型相關面試問題原理及舉例 目錄 大模型相關面試問題原理及舉例Transformer相關面試問題原理及舉例大模型模型結構相關面試問題原理及舉例注意力機制相關面試問題原理及舉例大模型與傳統模型區別 原理:大模型靠海量參數和復雜結構,能學習更復雜模式。傳統模型參數少、結構…

【AI+HR實戰應用】用DeepSeek提升HR工作效能

用DeepSeek提升HR工作效能 一、AI 與 AIGC 簡介二、DeepSeek 介紹三、使用 DeepSeek 的渠道及硬件要求四、使用 DeepSeek 的核心技巧五、AI 在人力資源的應用場景六、AI 繪畫與多模態應用七、個人使用 AI 的能力層級八、企業擁抱 AI 的策略九、提示詞管理的重要性 一、AI 與 AI…

Postgresql幾個常用的json操作

將行記錄轉為jsonb row_to_json(表名或別名)將行記錄集轉為json數組 &#xff08;jsonb) select json_agg(row_to_json(t) order by t.task_name) into v_next_taskfrom dyna_flow_task t where t.zidv_template_id and t.levelv_next_level ;訪問json字段&#xff0c;用->…

ESP32學習與快速總結——5.系統存儲

1.ESP32分區表 為什么ESP32要分區 00&#xff1a;34-- 簡述&#xff1a;其他單片機生成文件少&#xff0c;功能少&#xff0c;而ESP32功能多&#xff0c;文件多 分區表各個文件簡介 --7&#xff1a;31vscode查看分區表 --9&#xff1a;33ota通過idf.py menuconfi…

Linux 進程控制(自用)

非阻塞調用waitpid 這樣父進程就不會阻塞&#xff0c;此時循環使用我們可以讓父進程執行其他任務而不是阻塞等待 進程程序替換 進程PCB加載到內存中的代碼和數據 替換就是完全替換當前進程的代碼段、數據段、堆和棧&#xff0c;保存當前的PCB 代碼指的是二進制代碼不是源碼&a…

Spring 微服務解決了單體架構的哪些痛點?

1. 部署困難 (Deployment Difficulty & Risk) 單體痛點: 整體部署: 對單體應用的任何微小修改&#xff08;哪怕只是一行代碼&#xff09;&#xff0c;都需要重新構建、測試和部署整個龐大的應用程序。部署頻率低: 由于部署過程復雜且風險高&#xff0c;發布周期通常很長&a…

面試題之高頻面試題

最近開始面試了&#xff0c;410面試了一家公司 針對自己薄弱的面試題庫&#xff0c;深入了解下&#xff0c;也應付下面試。在這里先祝愿大家在現有公司好好沉淀&#xff0c;定位好自己的目標&#xff0c;在自己的領域上發光發熱&#xff0c;在自己想要的領域上&#xff08;技術…

【MySQL】Read view存儲的機制,記錄可見分析

read view核心組成 1.1 事務id相關 creator_trx_id: 創建該read view的事務id 每開啟一個事務都會生成一個 ReadView&#xff0c;而 creator_trx_id 就是這個開啟的事務的 id。 m_ids: 創建read view時系統的活躍事務&#xff08;未提交的事務&#xff09;id集合 當前有哪些事…

【刷題Day20】TCP和UDP(淺)

TCP 和 UDP 有什么區別&#xff1f; TCP提供了可靠、面向連接的傳輸&#xff0c;適用于需要數據完整性和順序的場景。 UDP提供了更輕量、面向報文的傳輸&#xff0c;適用于實時性要求高的場景。 特性TCPUDP連接方式面向連接無連接可靠性提供可靠性&#xff0c;保證數據按順序…