摘要:如何在初學就理解閉包你需要接著讀下去。這樣定義閉包是函數和聲明該函數的詞法環境的組合。小結閉包在中隨處可見。閉包是中的精華部分,理解它需要具備一定的作用域執行棧的知識。
這是本系列的第 4 篇文章。
作為 JS 初學者,第一次接觸閉包的概念是因為寫出了類似下面的代碼:
for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).click = function() { showHelp(item.help); } }
給列表項循環添加事件處理程序。當你點擊列表項時不會有任何反應。如何在初學就理解閉包?你需要接著讀下去。
§ 什么是閉包說閉包前,你還記得詞法作用域嗎?
var num = 0; function foo() { var num = 1; function bar() { console.log(num); } bar(); } foo(); // 1
執行上面的代碼打印出 1。
bar 函數是 foo 函數的內部函數,JS 的詞法作用域允許內部函數訪問外部函數的變量。那我們可不可以在外部訪問內部函數的變量呢?理論上不允許。
但是我們可以通過某種方式實現,即將內部函數返回。
function increase() { let count = 0; function add () { count += 1; return count; } return add; } const addOne = increase(); addOne(); // 1 addOne(); // 2 addOne(); // 3
內部函數允許訪問其父函數的內部變量,那么將內部函數返回到出來,它依舊引用著其父函數的內部變量。
這里就產生了閉包。
簡單來說,可以把閉包理解為函數返回函數。
上面的代碼中,當 increase 函數執行,壓入執行棧,執行完畢返回一個 add 函數的引用,所以 increase 函數內部的變量對象依舊保存在內存中,不會被銷毀。
調用 addOne 函數,相當于執行內部函數 add,它可以訪問其父函數的內部變量,從而修改變量 count。而調用 addOne 函數所在的環境為全局作用域,不是定義 add 函數時的函數作用域。
所以,我理解的閉包是一個函數,它在執行時與其定義時所處的詞法作用域不一致,并且具有能夠訪問定義時詞法作用域的能力。MDN 這樣定義:閉包是函數和聲明該函數的詞法環境的組合。
§ 閉包的利與弊 ◆ 利第一,閉包可以在函數外部讀取函數內部的變量。
var Counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })(); Counter.value(); // 0 Counter.increment(); Counter.increment(); Counter.value(); // 2 Counter.decrement(); Counter.value(); / 1
上面這種模式稱為模塊模式。我們使用立即執行函數 IIFE 將代碼私有化但是提供了可訪問的接口,通過公共接口來訪問函數私有的函數和變量。
第二,閉包將內部變量始終保存在內存中。
function type(tag) { return function (data) { return Object.prototype.toString.call(data).toLowerCase() === "[object " + tag + "]"; } } var isNum = type("number"); var isString = type("string"); isNum(1); // true isString("abc"); // true
利用閉包將內部變量(參數)tag 保存在內存中,來封裝自己的類型判斷函數。
◆ 弊第一,既然閉包會將內部變量一直保存在內存中,如果在程序中大量使用閉包,勢必造成內存的泄漏。
$(document).ready(function() { var button = document.getElementById("button-1"); button.onclick = function() { console.log("hello"); return false; }; });
在這個例子中,click 事件處理程序就是一個閉包(在這里是個匿名函數),它將引用著 button 變量;而 button 在這里本身依舊引用著這個匿名函數。從而產生循環引用,造成網頁的性能問題,在 IE 中可能會內存泄漏。
解決辦法就是手動解除引用。
$(document).ready(function() { var button = document.getElementById("button-1"); button.onclick = function() { console.log("hello"); return false; }; button = null; // 添加這一行代碼來手動解除引用 });
第二,如果你將函數作為對象使用,將閉包作為它的方法,應該特別注意不要隨意改動函數的私有屬性。
§ 閉包的經典問題 ◆ 循環現在我們來解決一下文章開頭出現的問題。
function makeHelpCallback(help) { return function() { showHelp(help); }; } for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).click = makeHelpCallback(item.help); }
額外聲明一個 makeHelpCallBack 的函數,將循環每次的上下文環境通過閉包保存起來。
◆ setTimeoutfor (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000); };
結果為 1 秒后,打印 5 個 5。
我們可以利用閉包保留詞法作用域的特點,來修改代碼達到目的。
for (var i = 0; i < 5; i++) { setTimeout((function(i) { return function () { console.log(i); } }(i)), 1000); };
結果為 1 秒后,依次打印 0 1 2 3 4。
§ 小結閉包在 JS 中隨處可見。
閉包是 JS 中的精華部分,理解它需要具備一定的作用域、執行棧的知識。理解它你將收獲巨大,你會在 JS 學習的道路上走得更遠,比如會在后面的文章來討論高階函數和柯里化的問題。
◆ 文章參考閉包 | MDN
學習 JavaScript 閉包 | 阮一峰
Understanding JavaScript Closures: A practical Approach | Paul Upendo
閉包造成問題泄漏的解決辦法 | CSDN
§ JavaScript 系列文章理解 JavaScript 執行棧
理解 JavaScript 作用域
理解 JavaScript 數據類型與變量
歡迎關注我的公眾號 cameraee
前端技術 | 個人成長
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/100131.html
摘要:但是閉包也不是什么復雜到不可理解的東西,簡而言之,閉包就是閉包就是函數的局部變量集合,只是這些局部變量在函數返回后會繼續存在。可惜的是,并沒有提供相關的成員和方法來訪問閉包中的局部變量。 (收藏自 技術狂) 前言:還是一篇入門文章。Javascript中有幾個非常重要的語言特性——對象、原型繼承、閉包。其中閉包 對于那些使用傳統靜態語言C/C++的程序員來說是一個新的語言特性。本文將...
摘要:也許最好的理解是閉包總是在進入某個函數的時候被創建,而局部變量是被加入到這個閉包中。在函數內部的函數的內部聲明函數是可以的可以獲得不止一個層級的閉包。 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包,大牛和功能型程序員請自行忽略。 譯者 :文章寫在2006年,可直到翻譯的21小時之前作者還在完善這篇文章,在Stackoverflow的How do Java...
摘要:當面試中讓我解釋一下閉包時我懵逼了。這個解釋開始可能有點晦澀,讓我們抽絲剝繭摘下閉包的真面目。此文不詳述作用域有專門的主題闡述,不過作用域是理解閉包原理的基礎。這才是閉包的真正便利之處。閉包使用不當就會很坑。 原文鏈接 為什么深度學習JavaScript? JavaScript如今是最流行的編程語言之一。它運行在瀏覽器、服務器、移動設備、桌面應用,也可能包括冰箱。無需我舉其他再多不相干...
摘要:譯者按在上一篇博客,我們通過實現一個計數器,了解了如何使用閉包,這篇博客將提供一些代碼示例,幫助大家理解閉包。然而,如果通過代碼示例去理解閉包,則簡單很多。不過,將閉包簡單地看做局部變量,理解起來會更加簡單。 - 譯者按: 在上一篇博客,我們通過實現一個計數器,了解了如何使用閉包(Closure),這篇博客將提供一些代碼示例,幫助大家理解閉包。 原文: JavaScript Clos...
摘要:閉包引起的內存泄漏總結從理論的角度將由于作用域鏈的特性中所有函數都是閉包但是從應用的角度來說只有當函數以返回值返回或者當函數以參數形式使用或者當函數中自由變量在函數外被引用時才能成為明確意義上的閉包。 文章同步到github js的閉包概念幾乎是任何面試官都會問的問題,最近把閉包這塊的概念梳理了一下,記錄成以下文章。 什么是閉包 我先列出一些官方及經典書籍等書中給出的概念,這些概念雖然...
閱讀 3223·2021-09-30 09:48
閱讀 3498·2021-09-22 16:00
閱讀 1071·2019-08-30 13:08
閱讀 3111·2019-08-30 10:53
閱讀 2422·2019-08-29 18:33
閱讀 1596·2019-08-29 12:47
閱讀 905·2019-08-29 12:16
閱讀 1937·2019-08-26 12:02