場景一:採用函數引用方式的setTimeout呼叫
閉包的一個通常的用法是為一個在某一函數執行前先執行的函數提供參數。例如,在web環境中,一個函數作為setTimeout函數呼叫的第一個參數,是一種很常見的應用。
setTimeout將要執行的函數(或一段javascript程式碼,但這不是我們要討論的情況)作為它的第一個參數,下一個參數是需要延遲執行的時間。如果一段程式碼想要透過setTimeout來調用,那麼它需要傳遞一個函數物件的參考來作為第一個參數。延遲的毫秒數作為第二個參數,但這個函數物件的參考無法為將要延遲執行的物件提供參數。
但是,可以呼叫另一個函數來傳回一個內部函數的調用,將那個內部函數物件的參考傳遞給setTimeout函數。內部函數執行時所需的參數,在呼叫外部函數時傳遞給它。 setTimeout執行內部函數時不需要傳遞參數,因為內部函數仍然能夠存取外部函數呼叫時提供的參數:
function callLater(paramA, paramB, paramC) { /*使用函数表达式创建并放回一个匿名内部函数的引用*/ return (function () { /* 这个内部函数将被setTimeout函数执行; 并且当它被执行时, 它能够访问并操作外部函数传递过来的参数 */ paramA[paramB] = paramC; }); } /* 调用这个函数将在它的执行上下文中创建,并最终返回内部函数对象的引用 传递过来的参数,内部函数在最终被执行时,将使用外部函数的参数 返回的引用被赋予了一个变量 */ var funcRef = callLater(elStyle, "display", "none"); /*调用setTimeout函数,传递内部函数的引用作为第一个参数*/ hideMenu = setTimeout(funcRef, 500);
場景二:將函數關聯到物件的實例方法
有很多這樣的場景:需要指派一個函數物件的引用,以便在未來的某個時間執行該函數。那麼閉包對於為這個將要執行的函數提供參考會非常有幫助。因為該函數可能直到執行時才能夠被存取。
有一個例子就是,一個javascript物件被封裝用來參與一個特定DOM元素的互動。它有doOnClick、doMouseOver以及doMouseOut方法。並且想在DOM元素上對應的事件被觸發時執行這些方法。但是,可能會有關聯著DOM元素的任意數量的javascript物件被創建,並且單一的實例並不知道那些實例化它們的程式碼將如何處理它們。物件實例不知道怎樣去「全域」引用它們自己,因為它們不知道哪個全域變數(如果存在)的參考將被指派給它們。
所以,問題是執行一個與特定javascript物件實例關聯的事件處理函數,並且知道呼叫那個物件的哪個方法。
接下來的一個例子,在有元素事件處理的物件實例的關聯函數上使用一個簡單的閉包。透過傳遞event物件以及要關聯元素的一個引用,為事件處理器分配不同的物件實例方法以供呼叫。
/* 一个给对象实例关联一个事件处理器的普通方法, 返回的内部函数被作为事件的处理器, 对象实例被作为obj参数,对象上将要被调用的方法名称被作为第二个参数 */ function associateObjWithEvent(obj, methodName) { /*返回的内部函数被用来作为一个DOM元素的事件处理器*/ return (function (e) { /* 事件对象在DOM标准的浏览器中将被转换为e参数, 如果没有传递参数给事件处理内部函数,将统一处理成IE的事件对象 */ e = e || window.event; /* 事件处理器调用obj对象上的以methodName字符串标识的方法 并传递两个对象:通用的事件对象,事件处理器被订阅的元素的引用 这里this参数能够使用,因为内部函数已经被执行作为事件处理器所在元素的一个方法 */ return obj[methodName](e, this); }); } /* 这个构造器函数,通过将元素的ID作为字符串参数传递进来, 来创建将自身关联到DOM元素上的对象, 对象实例想在对应的元素触发onclick、onmouseover、onmouseout事件时 对应的方法被调用。 */ function DhtmlObject(elementId) { /* 调用一个方法来获得一个DOM元素的引用 如果没有找到,则为null */ var el = getElementWith(elementId); /* 因为if语句块,el变量的值在内部进行了类型转换,变成了boolean类型 所以当它指向一个对象,结果就为true,如果为null则为false */ if (el) { /* 为了给元素指定一个事件处理函数,调用了associateObjWithEvent函数, 利用它自己(this关键字)作为被调用方法的对象,并且提供方法名称 */ el.onclick = associateObjWithEvent(this, "doOnClick"); el.onmouseover = associateObjWithEvent(this, "doOnMouseOver"); el.onmouseout = associateObjWithEvent(this, "doOnMouseOut"); } } DhtmlObject.prototype.doOnClick = function (event, element) { //doOnClick body } DhtmlObject.prototype.doMouseOver = function (event, element) { //doMouseOver body } DhtmlObject.prototype.doMouseOut = function (event, element) { //doMouseOut body }
任何DhtmlObject的實例都能夠將它們自身關聯到它們感興趣的DOM元素上去,不需要去擔心這些元素將被其他的程式碼怎麼處理,以及被全域命名空間「污染」或與其他的DhtmlObject的實例產生衝突。
場景三:封裝相關的功能集
閉包可以建立額外的scope,可以用來組合相關的或有依賴性的程式碼。用這種方式可以最大限度地減少程式碼幹擾的危害。假設,一個函數被用來創建一個字串並且避免重複串聯的操作(例如創建一系列的中間字串)。一個想法是,用一個陣列來順序儲存字串的一部分,然後使用Array.prototype.join方法輸出結果(使用一個空字串作為它的參數)。陣列將扮演輸出緩衝區的角色,但局部定義它又將會導致它在函數的每次執行時再次建立。如果這個數組只是作為唯一的變數被分配給每一個函數調用,這將會有點小題大做。
一個解決方案是將數組提升為全域變量,讓它不需要被再次創建也能夠再次使用。但結果並不是想的那麼簡單,另外,一個全域變數關聯這使用緩衝數組的函數,那將會有第二個全域屬性(函數本身也是window物件的屬性)關聯這個數組,這會讓程式碼失去一定的可控性。因為如果將它使用在其他地方。這段程式碼的創建者不得不記住包含函數的定義以及陣列的定義邏輯。它也使得程式碼不那麼容易與其他程式碼整合,因為將從原來只需要確定函數名稱是否在全域命名空間中唯一,變成有必要確定和該函數關聯的陣列的名稱是否在全域命名空間中保持唯一。
一個閉包可以讓緩衝數組關聯(乾淨地包含)它依賴的函數,並且同時保持緩衝數組的屬性名稱,像被分配在全局空間中一樣,同時能夠避免名稱衝突以及代碼交互幹擾的危險。
這裡有一招就是透過執行一個內聯的函數表達式來建立一個額外的執行上下文,讓那個函數表達式傳回一個內聯的函數,該函數被外部程式碼使用。緩衝數組被定義在內聯執行的函數表達式中,作為局部變數。它只被呼叫一次,所以該數組只被創建一次。但是對於依賴它的函數來說該數組是一直可存取的,並且可被重複使用的。
接一下的程式碼建立了一個函數,將傳回一個HTML字串,其中的一部分是不變的,但那些不變的字串需要被穿插進作為參數傳遞進來的變數中。
一个内联执行的函数表达式返回了内部函数对象的一个引用。并且分配了一个全局变量,让它可以被作为一个全局函数来调用。而缓冲数组作为一个局部变量被定义在外部函数表达式中。它没有被扩展到全局命名空间中,并且无论函数什么时候使用它都不需要被再次创建。
/* 定义一个全局变量:getImgInPositionedDivHtml 被赋予对外部函数表达式一次调用返回的一个内部函数表达式 内部函数返回了一个HTML字符串,代表一个绝对定位的DIV 包裹这一个IMG元素,而所有的变量值都被作为函数调用的参数 */ var getImgInPositionedDivHtml = (function () { /* buffAr 数组被定义在外部函数表达式中,作为一个局部变量 它只被创建一次。数组的唯一实例对内部函数是可见的, 所以它可以被用于每一次的内部函数执行 空字符串仅仅被用来作为一个占位符,它将被内部函数的参数代替 */ var buffAr = [ '<div id="', '', //index 1, DIV ID attribute '" style="position:absolute;top:', '', //index 3, DIV top position 'px;left:', '', //index 5, DIV left position 'px;width:', '', //index 7, DIV width 'px;height:', '', //index 9, DIV height 'px;overflow:hidden;\"><img src=\"', '', //index 11, IMG URL '\" width=\"', '', //index 13, IMG width '\" height=\"', '', //index 15, IMG height '\" alt=\"', '', //index 17, IMG alt text '\"><\/div>' ]; /* 返回一个内部函数对象,他是函数表达式执行返回的结果 */ return (function (url, id, width, height, top, left, altText) { /* 分配各种参数给对应的数组元素 */ buffAr[1] = id; buffAr[3] = top; buffAr[5] = left; buffAr[13] = (buffAr[7] = width); buffAr[15] = (buffAr[9] = height); buffAr[11] = url; buffAr[17] = altText; /* 返回连接每个元素后创建的字符串 */ return buffAr.join(''); }); })();
如果一个函数依赖另一个或几个函数,但那些其他的函数并不期望与任何其他的代码产生交互。那么这个简单的技巧(使用一个对外公开的函数来扩展那些函数)就可以被用来组织那些函数。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
以上是關於在JS中閉包可被利用的常見場景有哪幾種? (圖文教學)的詳細內容。更多資訊請關注PHP中文網其他相關文章!