摘要:權威指南第版中閉包的定義函數對象可以通過作用域鏈相互關聯起來,函數體內部的變量都可以保存在函數作用域內,這種特性在計算機科學文獻中成為閉包。循環中的閉包使用閉包時一種常見的錯誤情況是循環中的閉包,很多初學者都遇到了這個問題。
閉包簡介
閉包是JavaScript的重要特性,那么什么是閉包?
《JavaScript高級程序設計(第3版)》中閉包的定義:
閉包就是指有權訪問另一個函數中的變量的函數。
《JavaScript權威指南(第6版)》中閉包的定義:
函數對象可以通過作用域鏈相互關聯起來,函數體內部的變量都可以保存在函數作用域內,這種特性在計算機科學文獻中成為“閉包”。
簡單來說,在JavaScript中,函數是對象,對象是屬性的集合,屬性的值也可以是對象,在函數內定義函數就成為一種常見的情況,在函數內部聲明函數innerFunction,在innerFunction內部有權訪問外部函數的變量對象,這個函數就是我們所說的閉包。
我們來看一個簡單的例子:
function checkScope(){ var scope = "local scope"; function f() { return scope; } return f(); } checkScope();//輸出為“local scope”
當函數第一次被調用時,會創建一個執行環境以及相應的作用域鏈,作用域鏈的前端,始終都是當前執行代碼所在環境的變量對象,作用域鏈中的下一個變量對象來自包含外部環境,下一個變量則來自下一個外部環境,這樣一直延續到全局執行環境。
在上邊的例子中,訪問scope時,內部的f()函數可以訪問f()外部的變量scope,因為它在作用域鏈中一級一級往上找的時候可以找到scope變量。
閉包的作用
一、 模擬私有變量。在函數內創建一個閉包,閉包就可以通過自己的作用域鏈訪問函數內部的變量,可以創建用于訪問私有變量的方法。訪問私有變量和私有函數的方法被稱為特權方法。
function MyObject(){ var privateVariable = 10; function privateFunction() { return false; } //特權方法 this.publicFunction = function() { privateVariable++; return privateFunction(); }; }
二、 模仿塊級作用域。 JavaScript中沒有塊級作用域的概念,這意味著在塊語句中定義的變量,實際上是包含在函數中的。如果臨時需要一些變量,使用私有作用域。
function block() { var a = 1; var b = 2; (function () { var a = 3;//覆蓋了父作用域中的變量a var c = 4; //訪問到了當前作用域中的變量 console.log(a);//3 //訪問了父作用域中的變量 console.log(b);//2 //訪問當前作用域中的變量 console.log(c);//4 })() //訪問塊級作用域中的變量 console.log(c);//c is not defined }
這種技術經常在全局作用域中被用在函數外部,從而限制向全局作用域中添加過多的變量和函數。
循環中的閉包
使用閉包時一種常見的錯誤情況是循環中的閉包,很多初學者都遇到了這個問題。很常見的一種情況就是給頁面中的多個按鈕綁定點擊事件,JavaScript代碼如下所示:
window.onload = function(){ var inputs = document.getElementsByTagName("input"); for(var i = 0; i < inputs.length; i++){ inputs[i].onclick = function(){ console.log(i);//希望輸出0,1,2,3,4... } } }
頁面中有5個按鈕,根據上邊的代碼,我們需要的是依次點擊按鈕時,控制臺分別打印出0,1,2,3,4,而實際上,控制臺打印出來的,如下圖所示:
這是為什么呢,當for循環執行完之后,i已經變成了按鈕的個數5了,而所有點擊函數綁定的都是同一個i,點擊按鈕時,打印出來的i也都變成了5了。
這一部分理解也可以參考http://www.cnblogs.com/qieguo...。
那么我們為了得到想要的結果,需要在每次循環中創建變量i的拷貝,下面提供三種方法。
第一、使用匿名包裝器
window.onload = function () { var inputs = document.getElementsByTagName("input"); for (var i = 0; i < inputs.length; i++) { (function (e) { inputs[i].onclick = function () { console.log(e); } })(i); } }
依次點擊按鈕,控制臺輸出如下:
第二、從匿名包裝器中返回一個函數:
window.onload = function () { var inputs = document.getElementsByTagName("input"); for (var i = 0; i < inputs.length; i++) { inputs[i].onclick = function (num) { return function () { console.log(num); }; } (i); } }
首先,定義了匿名函數,并將立即執行該匿名函數的結果賦值給數組,匿名函數有一個參數num,在調用每個函數時,我們傳入了變量i,函數按值傳遞,就將變量i的當前值復制給參數num。而在這個匿名函數內部,又創建并返回了一個訪問num的閉包,這樣一來,每個按鈕點擊函數都有自己num變量的一個副本,因此可以輸出各自不同的數值了。
第三、在循環中使用let
window.onload = function () { var inputs = document.getElementsByTagName("input"); for (let i = 0; i < inputs.length; i++) { inputs[i].onclick = function () { console.log(i); }; } }
let是ES6新增的命令,用法類似于var,但是所聲明的變量只能在let命令所在代碼塊內有效。上述代碼中,變量i是let聲明的,當前的i只在本輪循環有效。所以每一次循環的i其實都是一個新的變量。關于let的用法可參考《ECMAScript 6 入門》中第二章。
內存泄漏
產生內存泄漏的原因是IE9之前的版本對JScript對象和COM對象使用不同的垃圾收集例程,因此閉包在IE的這些版本中會導致一些問題。(JavaScript垃圾收集機制可參考《JavaScript高級程序設計(第3版)》4.3) 例如:
function assignHandle() { var element = document.getElementById("elementId"); element.onclick = function () { //element的onclick引用了匿名函數, //匿名函數又通過閉包引用了element,造成了循環引用 console.log(element.id); }; }
這個例子中,循環引用導致了element引用數至少為1,element所占內存就永遠不會被回收,從而導致了內存泄漏問題。要解決這個問題,就需要解除對DOM對象的引用,減少引用數,確保正常回收其所占用的內存。
引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明了一個變量并將一個引用類型值賦給該變量時,則這個值的引用次數就是1。如果同一個值又被賦給另一個變量,則該值的引用次數加1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數減1。當這個值的引用次數變成0時,則說明沒有辦法再次訪問這個值了,因而就可以將其占用的內存空間回收回來。
在采用引用計數策略的實現中,出現循環引用時,由于變量的引用次數永遠不會是0,函數被多次調用時,就會導致大量內存得不到回收。 這一部分理解可以參考MDN中JavaScript內存管理。
結語
JavaScript閉包是極其有用的特性,但是由于閉包會攜帶包含它的函數的作用域,占用更多內存,過多使用閉包可能會導致內存占用過多。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88242.html
摘要:閉包引起的內存泄漏總結從理論的角度將由于作用域鏈的特性中所有函數都是閉包但是從應用的角度來說只有當函數以返回值返回或者當函數以參數形式使用或者當函數中自由變量在函數外被引用時才能成為明確意義上的閉包。 文章同步到github js的閉包概念幾乎是任何面試官都會問的問題,最近把閉包這塊的概念梳理了一下,記錄成以下文章。 什么是閉包 我先列出一些官方及經典書籍等書中給出的概念,這些概念雖然...
摘要:當在中調用匿名函數時,它們用的都是同一個閉包,而且在這個閉包中使用了和的當前值的值為因為循環已經結束,的值為。最好將閉包當作是一個函數的入口創建的,而局部變量是被添加進這個閉包的。 閉包不是魔法 這篇文章使用一些簡單的代碼例子來解釋JavaScript閉包的概念,即使新手也可以輕松參透閉包的含義。 其實只要理解了核心概念,閉包并不是那么的難于理解。但是,網上充斥了太多學術性的文章,對于...
摘要:一言以蔽之,閉包,你就得掌握。當函數記住并訪問所在的詞法作用域,閉包就產生了。所以閉包才會得以實現。從技術上講,這就是閉包。執行后,他的內部作用域并不會消失,函數依然保持有作用域的閉包。 網上總結閉包的文章已經爛大街了,不敢說筆者這篇文章多么多么xxx,只是個人理解總結。各位看官瞅瞅就好,大神還希望多多指正。此篇文章總結與《JavaScript忍者秘籍》 《你不知道的JavaScri...
摘要:注意由于閉包會額外的附帶函數的作用域內部匿名函數攜帶外部函數的作用域,因此,閉包會比其它函數多占用些內存空間,過度的使用可能會導致內存占用的增加。 作用域和作用域鏈是javascript中非常重要的特性,對于他們的理解直接關系到對于整個javascript體系的理解,而閉包又是對作用域的延伸,也是在實際開發中經常使用的一個特性,實際上,不僅僅是javascript,在很多語言中都...
摘要:也許最好的理解是閉包總是在進入某個函數的時候被創建,而局部變量是被加入到這個閉包中。在函數內部的函數的內部聲明函數是可以的可以獲得不止一個層級的閉包。 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包,大牛和功能型程序員請自行忽略。 譯者 :文章寫在2006年,可直到翻譯的21小時之前作者還在完善這篇文章,在Stackoverflow的How do Java...
閱讀 3744·2021-09-09 09:33
閱讀 3028·2019-08-30 15:56
閱讀 3021·2019-08-30 15:56
閱讀 3312·2019-08-30 15:55
閱讀 505·2019-08-30 15:53
閱讀 2186·2019-08-30 15:52
閱讀 672·2019-08-28 18:16
閱讀 2407·2019-08-26 13:51