一、前言
有准備去看Javascript框架,所以對於Javascript和原型繼承有必要去了解,這邊小記一下閉包比較好的例子~
二、正文
先來一下“閉包”的解釋~
一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。
閉包的特點:
1) 作為一個函數變量的一個引用,當函數返回時,其處於激活狀態。
2) 一個閉包就是當一個函數返回時,一個沒有釋放資源的棧區。
簡單的說,javascript允許使用內部函數---即函數定義和函數表達式位於另一個函數的函數體內。而且,這些內部函數可以訪問它們所在的外部函數中聲明的所有局部變量、參數和聲明的其他內部函數。當其中一個這樣的內部函數在包含它們的外部函數之外被調用時,就會形成閉包。
場景一:采用函數引用方式的setTimeout調用
閉包的一個通常的用法是為一個在某一函數執行前先執行的函數提供參數。例如,在web環境中,一個函數作為setTimeout函數調用的第一個參數,是一種很常見的應用。setTimeout將要執行的函數(或者一段JavaScript代碼,但這不是我們要討論的情況)作為它的第一個參數,下一個參數是需要延遲執行的時間。如果一段代碼想通過setTimeout來調用,那么它需要傳遞一個函數對象的引用來作為第一個參數。延遲的毫秒數作為第二個參數,但這個函數對象的引用無法為將要被延遲執行的對象提供參數。但是,可以調用另一個函數來返回一個內部函數的調用,將那個內部函數對象的引用傳遞給setTimeout函數。內部函數執行時需要的參數,在調用外部函數時傳遞給它。setTimeout在執行內部函數時無需傳遞參數,因為內部函數仍然能夠訪問外部函數調用時提供的參數:1 function callLater(paramA, paramB, paramC) { 2 /*使用函數表達式創建並放回一個匿名內部函數的引用*/ 3 return (function () { 4 /* 5 這個內部函數將被setTimeout函數執行; 6 並且當它被執行時, 7 它能夠訪問並操作外部函數傳遞過來的參數 8 */ 9 paramA[paramB] = paramC; 10 }); 11 } 12 13 /* 14 調用這個函數將在它的執行上下文中創建,並最終返回內部函數對象的引用 15 傳遞過來的參數,內部函數在最終被執行時,將使用外部函數的參數 16 返回的引用被賦予了一個變量 17 */ 18 var funcRef = callLater(elStyle, "display", "none"); 19 /*調用setTimeout函數,傳遞內部函數的引用作為第一個參數*/ 20 hideMenu = setTimeout(funcRef, 500);
場景二:將函數關聯到對象的實例方法
有很多這樣的場景:需要分配一個函數對象的引用,以便在未來的某個時間執行該函數。那么閉包對於為這個將要執行的函數提供引用會非常有幫助。因為該函數可能直到執行時才能夠被訪問。
有一個例子就是,一個javascript對象被封裝用來參與一個特定DOM元素的交互。它有doOnClick、doMouseOver以及doMouseOut方法。並且想在DOM元素上對應的事件被觸發時執行這些方法。但是,可能會有關聯着DOM元素的任意數量的javascript對象被創建,並且單個的實例並不知道那些實例化它們的代碼將如何處理它們。對象實例不知道怎樣去“全局”地引用它們自己,因為它們不知道哪個全局變量(如果存在)的引用將被分配給它們。
所以,問題是執行一個與特定javascript對象實例關聯的事件處理函數,並且知道調用那個對象的哪個方法。
接下來的一個例子,在有元素事件處理的對象實例的關聯函數上使用一個簡單的閉包。通過傳遞event對象以及要關聯元素的一個引用,為事件處理器分配不同的對象實例方法以供調用。
1 /* 2 一個給對象實例關聯一個事件處理器的普通方法, 3 返回的內部函數被作為事件的處理器, 4 對象實例被作為obj參數,對象上將要被調用的方法名稱被作為第二個參數 5 */ 6 function associateObjWithEvent(obj, methodName) { 7 /*返回的內部函數被用來作為一個DOM元素的事件處理器*/ 8 return (function (e) { 9 /* 10 事件對象在DOM標准的瀏覽器中將被轉換為e參數, 11 如果沒有傳遞參數給事件處理內部函數,將統一處理成IE的事件對象 12 */ 13 e = e || window.event; 14 /* 15 事件處理器調用obj對象上的以methodName字符串標識的方法 16 並傳遞兩個對象:通用的事件對象,事件處理器被訂閱的元素的引用 17 這里this參數能夠使用,因為內部函數已經被執行作為事件處理器所在元素的一個方法 18 */ 19 return obj[methodName](e, this); 20 }); 21 } 22 23 /* 24 這個構造器函數,通過將元素的ID作為字符串參數傳遞進來, 25 來創建將自身關聯到DOM元素上的對象, 26 對象實例想在對應的元素觸發onclick、onmouseover、onmouseout事件時 27 對應的方法被調用。 28 */ 29 function DhtmlObject(elementId) { 30 /* 31 調用一個方法來獲得一個DOM元素的引用 32 如果沒有找到,則為null 33 */ 34 var el = getElementWith(elementId); 35 /* 36 因為if語句塊,el變量的值在內部進行了類型轉換,變成了boolean類型 37 所以當它指向一個對象,結果就為true,如果為null則為false 38 */ 39 if (el) { 40 /* 41 為了給元素指定一個事件處理函數,調用了associateObjWithEvent函數, 42 利用它自己(this關鍵字)作為被調用方法的對象,並且提供方法名稱 43 */ 44 el.onclick = associateObjWithEvent(this, "doOnClick"); 45 el.onmouseover = associateObjWithEvent(this, "doOnMouseOver"); 46 el.onmouseout = associateObjWithEvent(this, "doOnMouseOut"); 47 } 48 } 49 50 DhtmlObject.prototype.doOnClick = function (event, element) { 51 //doOnClick body 52 } 53 DhtmlObject.prototype.doMouseOver = function (event, element) { 54 //doMouseOver body 55 } 56 57 DhtmlObject.prototype.doMouseOut = function (event, element) { 58 //doMouseOut body 59 }
任何DhtmlObject的實例都能夠將它們自身關聯到它們感興趣的DOM元素上去,不需要去擔心這些元素將被其他的代碼怎么處理,以及被全局命名空間“污染”或者與其他的DhtmlObject的實例產生沖突。
場景三:封裝相關的功能集
閉包可以創建額外的scope,這可以被用來組合相關的或有依賴性的代碼。用這種方式可以最大限度地減少代碼干擾的危害。假設,一個函數被用來創建一個字符串並且避免重復串聯的操作(比如創建一系列的中間字符串)。一個想法是,用一個數組來順序存儲字符串的一部分,然后使用Array.prototype.join方法輸出結果(使用一個空字符串作為它的參數)。數組將扮演着輸出緩沖區的角色,但局部定義它又將會導致它在函數的每次執行時再次創建。如果這個數組只是作為唯一的變量被分配給每一個函數調用,這將會有點小題大做。
一個解決方案是將數組提升為全局變量,讓它不需要被再次創建也能夠再次使用。但結果並不是想的那么簡單,另外,一個全局變量關聯這使用緩沖數組的函數,那將會有第二個全局屬性(函數本身也是window對象的屬性)關聯這個數組,這將讓代碼失去一定的可控性。因為如果將它使用在其他地方。這段代碼的創建者不得不記住包含函數的定義以及數組的定義邏輯。它也使得代碼不那么容易與其他代碼整合,因為將從原來只需要確定函數名是否在全局命名空間中唯一,變成有必要確定和該函數關聯的數組的名稱是否在全局命名空間中保持唯一。
一個閉包可以讓緩沖數組關聯(干凈地包含)它依賴的函數,並且同時保持緩沖數組的屬性名稱,像被分配在全局空間中一樣,同時能夠避免名稱沖突以及代碼交互干擾的危險。
這里有一招就是通過執行一個內聯的函數表達式創建一個額外的執行上下文,讓那個函數表達式返回一個內聯的函數,該函數被外部代碼使用。緩沖數組被定義在內聯執行的函數表達式中,作為一個局部變量。它僅被調用一次,所以該數組只被創建一次。但是對於依賴它的函數來說該數組是一直可訪問的,並且可被重用的。
接一下的代碼創建了一個函數,將返回一個HTML字符串,其中的一部分是不變的,但那些不變的字符串需要被穿插進作為參數傳遞進來的變量中。
一個內聯執行的函數表達式返回了內部函數對象的一個引用。並且分配了一個全局變量,讓它可以被作為一個全局函數來調用。而緩沖數組作為一個局部變量被定義在外部函數表達式中。它沒有被擴展到全局命名空間中,並且無論函數什么時候使用它都不需要被再次創建。
1 /* 2 定義一個全局變量:getImgInPositionedDivHtml 3 被賦予對外部函數表達式一次調用返回的一個內部函數表達式 4 5 內部函數返回了一個HTML字符串,代表一個絕對定位的DIV 6 包裹這一個IMG元素,而所有的變量值都被作為函數調用的參數 7 */ 8 var getImgInPositionedDivHtml = (function () { 9 /* 10 buffAr 數組被定義在外部函數表達式中,作為一個局部變量 11 它只被創建一次。數組的唯一實例對內部函數是可見的, 12 所以它可以被用於每一次的內部函數執行 13 14 空字符串僅僅被用來作為一個占位符,它將被內部函數的參數代替 15 */ 16 var buffAr = [ 17 '<div id="', 18 '', //index 1, DIV ID attribute 19 '" style="position:absolute;top:', 20 '', //index 3, DIV top position 21 'px;left:', 22 '', //index 5, DIV left position 23 'px;width:', 24 '', //index 7, DIV width 25 'px;height:', 26 '', //index 9, DIV height 27 'px;overflow:hidden;\"><img src=\"', 28 '', //index 11, IMG URL 29 '\" width=\"', 30 '', //index 13, IMG width 31 '\" height=\"', 32 '', //index 15, IMG height 33 '\" alt=\"', 34 '', //index 17, IMG alt text 35 '\"><\/div>' 36 ]; 37 38 /* 39 返回一個內部函數對象,他是函數表達式執行返回的結果 40 */ 41 return (function (url, id, width, height, top, left, altText) { 42 /* 43 分配各種參數給對應的數組元素 44 */ 45 buffAr[1] = id; 46 buffAr[3] = top; 47 buffAr[5] = left; 48 buffAr[13] = (buffAr[7] = width); 49 buffAr[15] = (buffAr[9] = height); 50 buffAr[11] = url; 51 buffAr[17] = altText; 52 53 /* 54 返回連接每個元素后創建的字符串 55 */ 56 return buffAr.join(''); 57 }); 58 })();
如果一個函數依賴另一個或幾個函數,但那些其他的函數並不期望與任何其他的代碼產生交互。那么這個簡單的技巧(使用一個對外公開的函數來擴展那些函數)就可以被用來組織那些函數。