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