摘要:閉包的定義閉包是函數和聲明該函數的詞法作用域的組合。上面的和都是閉包。然而在一個閉包內對變量的修改,不會影響到另一個閉包中的變量。原因是賦值給的是閉包。由于循環在事件觸發之前早已執行完畢,變量被三個閉包共享已經變成了。
閉包的定義:
閉包是函數和聲明該函數的詞法作用域的組合。
先看如下例子:
function makeFn(){ var name = "Mirror"; function showName(){ alert(name) } return showName; } var myFn = makeFn(); myFn(); // "Mirror"
javascript 中的函數會形成閉包。閉包是由函數以及創建該函數的詞法環境組合組成。這個環境包含了這個閉包創建時所能訪問的所有局部變量。在以上的例子中,myFn是執行makeFn時創建的showName函數實例的引用,而showName實例仍可訪問其詞法作用域中的變量,既可以訪問到name。 由此,當myFn 被調用時,name仍可被訪問。
再看一個更有意思的例子:
function makeAdder(x){ return function( y ){ return x + y } } var add5 = makeAdder( 5 ); var add10 = makeAdder( 10 ); console.log( add5(2) ) // 7 console.log( add10(2) ) // 12
以上示例中,我們定義了makeAdder(x) 函數,它接收一個參數x ,并返回一個新的函數。返回的函數接受一個參數y,并返回 x+y的值。
本質上講,makeAdder 是一個工廠函數 -- 它創建了將指定的值和它的參數相加求和的函數。上面的add5和add10都是閉包。它們共享相同的函數定義,但是保存了不同的詞法環境。在add5的環境中,x為5,而在add10中,x則是10。
實用的閉包:閉包允許將函數與其所操作的某些數據(環境)關聯起來。這顯然類似于面向對象編程。在面向對象編程中,對象允許我們將某些數據(對象的屬性)與一個或者多個方法相關聯。
通常,使用只有一個方法的對象的地方都可以使用閉包。
假如,我們想在頁面上添加一些可以調整字號的按鈕。一種方法是以像素為單位指定 body 元素的 font-size,然后通過相對的 em 單位設置頁面中其它元素(例如header)的字號:
用閉包模擬私有方法:h1 標簽
h2 標簽
私有方法不僅僅有利于限制對代碼的訪問,還提供了管理全局命名空間的強大能力,避免非核心的方法弄亂了代碼的公共接口部分。
var Counter = ( function(){ var privateCounter = 0; function changeBy(val){ privateCounter += val }; return { add:function(){ changeBy(1); return privateCounter; }, decrease:function(){ changeBy(-1); return privateCounter; }, value:function(){ return privateCounter; } } })()
以上示例中我們只創建了一個詞法環境,為三個函數所共享:Counter.add、 Counter.decrease 和Counter.value
該共享環境創建一個立即執行的匿名函數體內。這個環境中包含兩個私有項:privateCounter的變量和changeBy的函數。這兩項都無法在這個匿名函數外直接訪問。必須通過匿名函數返回的三個公共函數訪問。
這三個公共函數是共享同一個環境的閉包。多虧javascript的詞法作用域,它們都可以訪問privateCounter變量和changeBy函數。
應該注意到我們定義了一個匿名函數,用來創建計算器。立即執行函數將他的值賦給了變量Counter。我們也可以將這個函數存儲在另一個變量makeCounter中,并用它來創建多個計數器。
var makeCounter = function(){ var privateCounter = 0; var changeBy = function(val){ privateCounter += val }; return { add:function(){ changeBy(1) return privateCounter }, decrease:function(){ changeBy(-1) return privateCounter }, value:function(){ return privateCounter } } } var Counter1 = makeCounter(); var Counter2 = makeCounter(); Counter1.add() // 1 Counter2.decrease() // -1
兩個計數器Counter1和Counter2都引用自己詞法作用域內的變量privateCounter。每次調用其中一個計數器時,通過改變這個變量的值,會改變這個閉包的詞法環境。然而在一個閉包內對變量的修改,不會影響到另一個閉包中的變量。
以這種方式使用閉包,提供了許多面向對象編程相關的好處——特別是數據隱藏和封裝。在循環中創建閉包:一個常見的錯誤
在ECMAScript2015引入let關鍵字以前,在循環中有一個常見的閉包創建問題。如下:
運行以上代碼,會發現沒有達到想要的結果。無論點擊哪個按鈕,彈窗提示的都是第3個按鈕。
原因是賦值給onclick的是閉包。三個閉包在循環中被創建,但是他們共享同一個詞法作用域,在這個作用域中存在一個變量i。當onclick的回調執行時,i的值被決定。由于循環在事件觸發之前早已執行完畢,變量i(被三個閉包共享)已經變成了"3"。
解決這個問題的一種方案是使用跟多的閉包:特別是使用前面所述的函數工廠:
這段代碼可以如我們所期望的那樣工作。所有的回調不再共享同一個環境,clickCallback函數為每一個回調創建一個新的詞法環境。在這些環境中。x指向循環中的i。
另一種方法實現了匿名閉包:
避免使用過多的閉包,可以用let關鍵詞:
這個例子使用的是let而不是var,因此每個閉包都綁定了塊作用域的變量,這意味著不再需要額外的閉包。
性能考量如果不是某些特定的任務需要使用閉包,在其他函數中創建函數是不明智的,因為閉包在處理速度和內存消耗方面對腳本性能具有負面影響。
例如,在創建新的對象或者類時,方法通常應該關聯于對象的原型,而不是定義到對象的構造器中。原因是這將導致每次構造器被調用時,方法都會被重新賦值一次(也就是,每個對象的創建)。
考慮一下示例:
function MyObject( name , message ){ this.name = name; this.message = message.toString(); this.getName = function(){ return this.name; }; this.getMessage = function(){ return this.message }; }
上面的代碼并未利用到閉包的好處,可以修改成如下:
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype = { getName: function() { return this.name; }, getMessage: function() { return this.message; } };
不建議重新定義原型。可改成如下例子:
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype.getName = function() { return this.name; }; MyObject.prototype.getMessage = function() { return this.message; };
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95458.html
摘要:也許最好的理解是閉包總是在進入某個函數的時候被創建,而局部變量是被加入到這個閉包中。在函數內部的函數的內部聲明函數是可以的可以獲得不止一個層級的閉包。 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包,大牛和功能型程序員請自行忽略。 譯者 :文章寫在2006年,可直到翻譯的21小時之前作者還在完善這篇文章,在Stackoverflow的How do Java...
摘要:但是閉包也不是什么復雜到不可理解的東西,簡而言之,閉包就是閉包就是函數的局部變量集合,只是這些局部變量在函數返回后會繼續存在。可惜的是,并沒有提供相關的成員和方法來訪問閉包中的局部變量。 (收藏自 技術狂) 前言:還是一篇入門文章。Javascript中有幾個非常重要的語言特性——對象、原型繼承、閉包。其中閉包 對于那些使用傳統靜態語言C/C++的程序員來說是一個新的語言特性。本文將...
摘要:當在中調用匿名函數時,它們用的都是同一個閉包,而且在這個閉包中使用了和的當前值的值為因為循環已經結束,的值為。最好將閉包當作是一個函數的入口創建的,而局部變量是被添加進這個閉包的。 閉包不是魔法 這篇文章使用一些簡單的代碼例子來解釋JavaScript閉包的概念,即使新手也可以輕松參透閉包的含義。 其實只要理解了核心概念,閉包并不是那么的難于理解。但是,網上充斥了太多學術性的文章,對于...
摘要:深入系列第八篇,介紹理論上的閉包和實踐上的閉包,以及從作用域鏈的角度解析經典的閉包題。定義對閉包的定義為閉包是指那些能夠訪問自由變量的函數。 JavaScript深入系列第八篇,介紹理論上的閉包和實踐上的閉包,以及從作用域鏈的角度解析經典的閉包題。 定義 MDN 對閉包的定義為: 閉包是指那些能夠訪問自由變量的函數。 那什么是自由變量呢? 自由變量是指在函數中使用的,但既不是函數參數也...
摘要:一言以蔽之,閉包,你就得掌握。當函數記住并訪問所在的詞法作用域,閉包就產生了。所以閉包才會得以實現。從技術上講,這就是閉包。執行后,他的內部作用域并不會消失,函數依然保持有作用域的閉包。 網上總結閉包的文章已經爛大街了,不敢說筆者這篇文章多么多么xxx,只是個人理解總結。各位看官瞅瞅就好,大神還希望多多指正。此篇文章總結與《JavaScript忍者秘籍》 《你不知道的JavaScri...
摘要:但是,必須強調,閉包是一個運行期概念。通過原型鏈可以實現繼承,而與閉包相關的就是作用域鏈。常理來說,一個函數執行完畢,其執行環境的作用域鏈會被銷毀。所以此時,的作用域鏈雖然銷毀了,但是其活動對象仍在內存中。 學習Javascript閉包(Closure)javascript的閉包JavaScript 閉包深入理解(closure)理解 Javascript 的閉包JavaScript ...
閱讀 2716·2021-09-24 09:47
閱讀 4366·2021-08-27 13:10
閱讀 2981·2019-08-30 15:44
閱讀 1281·2019-08-29 12:56
閱讀 2594·2019-08-28 18:07
閱讀 2615·2019-08-26 14:05
閱讀 2553·2019-08-26 13:41
閱讀 1265·2019-08-26 13:33