摘要:閉包引起的內存泄漏總結從理論的角度將由于作用域鏈的特性中所有函數都是閉包但是從應用的角度來說只有當函數以返回值返回或者當函數以參數形式使用或者當函數中自由變量在函數外被引用時才能成為明確意義上的閉包。
文章同步到github
js的閉包概念幾乎是任何面試官都會問的問題,最近把閉包這塊的概念梳理了一下,記錄成以下文章。
什么是閉包我先列出一些官方及經典書籍等書中給出的概念,這些概念雖然表達的不一樣,但是都在對閉包做了最正確的定義和翻譯,也幫助大家更好的理解閉包,這閱讀起來可能比較模糊,大家往后看,本文通過對多個經典書籍中的例子講解,相信會讓大家能很好的理解js中的閉包。文章開始,我會先鋪墊一下閉包的概念和為什么要引入閉包的概念,然后結合例子來說明講解,并講解如何使用閉包。
百度百科中的定義:閉包包含自由(未綁定到特定對象)變量;這些變量不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義(局部變量)。“閉包” 一詞來源于以下兩者的結合:要執行的代碼塊(由于自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和為自由變量提供綁定的計算環境(作用域) -- 百度百科
《javaScript權威指南》中的概念:函數對象可以通過作用域鏈互相關聯起來,函數體內部的變量都可以保存在函數作用域內,這種特性在計算機科學中成為閉包
《javaScript高級教程》中概念:閉包是指有權訪問另一個函數作用域中的變量的函數。
MDN中的概念 個人總結的閉包概念:閉包就是子函數可以有權訪問父函數的變量、父函數的父函數的變量、一直到全局變量。歸根結底,就是利用js得詞法(靜態)作用域,即作用域鏈在函數創建的時候就確定了。
子函數如果不被銷毀,整條作用域鏈上的變量仍然保存在內存中。
為什么引入閉包的概念我引入《深入理解JavaScript系列:閉包(Closures)》文章中的例子來說明,也可以直接去看那篇文章,我結合其他書籍反復讀了很多遍此文章才理解清楚。如下:
function testFn() { var localVar = 10; // 自由變量 function innerFn(innerParam) { alert(innerParam + localVar); } return innerFn; } var someFn = testFn(); someFn(20); // 30
一般來說,在函數執行完畢之后,局部變量對象即被銷毀,所以innerFn是不可能以返回值形式返回的,innerFn函數作為局部變量應該被銷毀才對。
這是當函數以返回值時的問題,另外再看一個當函數以參數形式使用時的問題,還是直接引用《深入理解JavaScript系列》中的例子,也方便大家有興趣可以直接去閱讀那篇文章
var z = 10; function foo() { alert(z); } foo(); // 10 – 使用靜態和動態作用域的時候 (function () { var z = 20; foo(); // 10 – 使用靜態作用域, 20 – 使用動態作用域 })(); // 將foo作為參數的時候是一樣的 (function (funArg) { var z = 30; funArg(); // 10 – 靜態作用域, 30 – 動態作用域 })(foo);
當函數foo在不同的函數中調用,z該取哪個上下文中的值呢,這就又是一個問題,所以就引入了閉包的概念,也可以理解為定義了一種規則。
理解閉包 函數以返回值返回看一個《javsScript權威指南》中的一個例子,我稍微做一下修改如下:
var scope = "global scope"; function checkScope() { var scope = "local scope"; return function() { console.log(scope); } } var result = checkScope(); result(); // local scope checkScope變量對象中的scope,非全局變量scope
分析:
即使匿名函數是在checkScope函數外調用,也沒有使用全局變量scope,即是利用了js的靜態作用域,當匿名函數初始化時,就創建了自己的作用域鏈(作用域鏈的概念這里不做解釋,可以參考我的另一篇文章js中的執行棧、執行環境(上下文)、作用域、作用域鏈、活動對象、變量對象的概念總結,其實當把作用域鏈理解好了之后,閉包也就理解了), 此匿名函數的作用域鏈包括checkScope的活動對象和全局變量對象, 當checkScope函數執行完畢后,checkScope的活動對象并不會被銷毀,因為匿名函數的作用域鏈還在引用checkScope的活動對象,也就是checkScope的執行環境被銷毀,但是其活動對象沒有被銷毀,留存在堆內存中,直到匿名函數銷毀后,checkScope的活動對象才會銷毀,解除對匿名函數的引用將其設置為null即可,垃圾回收將會將其清除,另外當外部對checkScope的自由變量存在引用的時候,其活動對象也不會被銷毀
result = null; //解除對匿名函數的引用
注釋:
自由變量是指在函數中使用的,但既不是函數參數也不是函數的局部變量的變量
補充:
引用一下《javsScript權威指南》中的補充,幫助大家進一步理解
當函數以參數形式使用時一般用于利用閉包特性解決實際問題,比如瀏覽器中內置的方法等,下面我直接引用《深入理解JavaScript系列:閉包(Closures)》中關于閉包實戰部分的例子如下:
sort在sort的內置方法中,函數以參數形式傳入回調函數,在sort的實現中調用:
[1, 2, 3].sort(function (a, b) { ... // 排序條件 });map
和sort的實現一樣
[1, 2, 3].map(function (element) { return element * 2; }); // [2, 4, 6]另外利用自執行匿名函數創建的閉包
var foo = {}; // 初始化 (function (object) { var x = 10; object.getX = function() { return x; }; })(foo); alert(foo.getX()); // 獲得閉包 "x" – 10利用閉包實現私有屬性的存取
先來看一個例子
var fnBox = []; function foo() { for(var i = 0; i < 3; i++) { fnBox[i] = function() { return i; } } } foo(); var fn0 = fnBox[0]; var fn1 = fnBox[1]; var fn2 = fnBox[2]; console.log(fn0()); // 3 console.log(fn1()); // 3 console.log(fn2()); // 3
用偽代碼來說明如下:
fn0.[[scope]]= { // 其他變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3] } fn1.[[scope]]= { // 其他變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3] } fn2.[[scope]]= { // 其他變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3], }
分析:
這是因為fn0、fn1、fn2的作用域鏈共享foo的活動對象, 而且js沒有塊級作用域,當函數foo執行完畢的時候foo的活動對象中i的值已經變為3,當fn0、fn1、fn2執行的時候,其最頂層的作用域沒有i變量,就沿著作用域鏈查找foo的活動對象中的i,所以i都為3。
但是這種結果往往不是我們想要的,這時就可以利用認為創建一個閉包來解決這個問題,如下:
var fnBox = []; function foo() { for(var i = 0; i < 3; i++) { fnBox[i] = (function(num) { return function() { return num; } })(i); } } foo(); var fn0 = fnBox[0]; var fn1 = fnBox[1]; var fn2 = fnBox[2]; console.log(fn0()); // 0 console.log(fn1()); // 1 console.log(fn2()); // 2
用偽代碼來說明如下:
fn0.[[scope]]= { // 其他變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3], fn0本身的活動對象AO: {num: 0} } fn1.[[scope]]= { // 其他變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3], fn1本身的活動對象AO: {num: 1} } fn2.[[scope]]= { // 其他變量對象,一直到全局變量對象 父級上下文中的活動對象AO: [data: [...], i: 3], fn2本身的活動對象AO: {num: 2} }
分析:
當使用自執行匿名函數創建閉包, 傳入i的值賦值給num,由于作用域鏈是在函數初始化時創建的,所以當每次循環時,函數fn10、fn1、fn2的作用域鏈中保存了當次循環是num的值, 當fn10、fn1、fn2調用時,是按照本身的作用域鏈進行查找。
閉包引起的內存泄漏 總結從理論的角度將,由于js作用域鏈的特性,js中所有函數都是閉包,但是從應用的角度來說,只有當函數以返回值返回、或者當函數以參數形式使用、或者當函數中自由變量在函數外被引用時,才能成為明確意義上的閉包。
最后,我想表達的式,本篇大量引用和羅列了經典的犀牛書《javaScript權威指南》、紅寶書《javaScript高級教程》、以及《深入理解JavaScript系列:閉包(Closures)》系列文章中的概念和例子,不為能形成自己的獨特見解,只為了能把閉包清晰的講解出來。筆者是個小菜鳥,能力實在有限,也在學習中,希望大家多多指點,如發現錯誤,請多多指正。也希望看過此文的朋友能對閉包多一些理解,那我寫這篇文章也就值得了。下次面試時就可以告訴面試官什么是閉包了。謝謝。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89142.html
摘要:當在中調用匿名函數時,它們用的都是同一個閉包,而且在這個閉包中使用了和的當前值的值為因為循環已經結束,的值為。最好將閉包當作是一個函數的入口創建的,而局部變量是被添加進這個閉包的。 閉包不是魔法 這篇文章使用一些簡單的代碼例子來解釋JavaScript閉包的概念,即使新手也可以輕松參透閉包的含義。 其實只要理解了核心概念,閉包并不是那么的難于理解。但是,網上充斥了太多學術性的文章,對于...
摘要:一言以蔽之,閉包,你就得掌握。當函數記住并訪問所在的詞法作用域,閉包就產生了。所以閉包才會得以實現。從技術上講,這就是閉包。執行后,他的內部作用域并不會消失,函數依然保持有作用域的閉包。 網上總結閉包的文章已經爛大街了,不敢說筆者這篇文章多么多么xxx,只是個人理解總結。各位看官瞅瞅就好,大神還希望多多指正。此篇文章總結與《JavaScript忍者秘籍》 《你不知道的JavaScri...
摘要:權威指南第版中閉包的定義函數對象可以通過作用域鏈相互關聯起來,函數體內部的變量都可以保存在函數作用域內,這種特性在計算機科學文獻中成為閉包。循環中的閉包使用閉包時一種常見的錯誤情況是循環中的閉包,很多初學者都遇到了這個問題。 閉包簡介 閉包是JavaScript的重要特性,那么什么是閉包? 《JavaScript高級程序設計(第3版)》中閉包的定義: 閉包就是指有權訪問另一個函數中的變...
摘要:注意由于閉包會額外的附帶函數的作用域內部匿名函數攜帶外部函數的作用域,因此,閉包會比其它函數多占用些內存空間,過度的使用可能會導致內存占用的增加。 作用域和作用域鏈是javascript中非常重要的特性,對于他們的理解直接關系到對于整個javascript體系的理解,而閉包又是對作用域的延伸,也是在實際開發中經常使用的一個特性,實際上,不僅僅是javascript,在很多語言中都...
摘要:也許最好的理解是閉包總是在進入某個函數的時候被創建,而局部變量是被加入到這個閉包中。在函數內部的函數的內部聲明函數是可以的可以獲得不止一個層級的閉包。 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包,大牛和功能型程序員請自行忽略。 譯者 :文章寫在2006年,可直到翻譯的21小時之前作者還在完善這篇文章,在Stackoverflow的How do Java...
閱讀 671·2023-04-25 18:59
閱讀 1211·2021-09-22 16:00
閱讀 1889·2021-09-22 15:42
閱讀 3594·2021-09-22 15:27
閱讀 1246·2019-08-30 15:54
閱讀 1104·2019-08-30 11:16
閱讀 2445·2019-08-29 16:24
閱讀 820·2019-08-29 12:14