2014-06-10 49 views
0

在另一個StackOverflow問題(Creating a clickable grid in a web browser)中,回答者發佈了此示例:http://jsfiddle.net/6qkdP/2/幫助理解函數回調

我正在嘗試在學習JavaScript時做類似的事情,但這個答案提供了比答案更多的問題。現在,我想我理解返回函數的函數的概念。然而,JS張貼的jsfiddle鏈路上混淆了我,這裏的原因......

第15行,創建功能:

function clickableGrid(rows, cols, callback){...} 

看似簡單:通過行和列數,並傳遞任何被調用的函數(對吧?)。在第2行中,它調用該函數,對於回調函數,它傳遞一個接受一些東西(el,row,col,i)的函數並在控制檯中提供輸出。

var grid = clickableGrid(10,10,function(el,row,col,i){...} 

這一切似乎很容易理解(當然,假設我理解正確!)。但是,然後從24行開始的這個位讓我感到困惑:

它爲正在創建的元素創建一個click事件偵聽器。此事件監聽器調用接受4個參數的新函數... cell.addEventListener('click',(function(el,r,c,i)){

...返回另一個函數...

   return function(){ 

...這是主要功能的回調,路過那些相同的4個參數(?)...

    callback(el,r,c,i); 
       } 

...然後,該位是在點擊期間所獲取傳遞事件?

  })(cell,r,c,i),false); 

是嗎?這一切似乎對我來說過分困惑......例如,我不明白爲什麼創建一個接受相同參數的函數,並在不需要所有這些回調的情況下立即改變元素的樣式?我錯過了一些東西,並希望這裏更有經驗的人可以提供關於我沒有掌握的東西的信息。

謝謝!

代碼,萬一的jsfiddle下跌:

var lastClicked; 
var grid = clickableGrid(10,10,function(el,row,col,i){ 
    console.log("You clicked on element:",el); 
    console.log("You clicked on row:",row); 
    console.log("You clicked on col:",col); 
    console.log("You clicked on item #:",i); 

    el.className='clicked'; 
    if (lastClicked) lastClicked.className=''; 
    lastClicked = el; 
}); 

document.body.appendChild(grid); 

function clickableGrid(rows, cols, callback){ 
    var i=0; 
    var grid = document.createElement('table'); 
    grid.className = 'grid'; 
    for (var r=0;r<rows;++r){ 
     var tr = grid.appendChild(document.createElement('tr')); 
     for (var c=0;c<cols;++c){ 
      var cell = tr.appendChild(document.createElement('td')); 
      cell.innerHTML = ++i; 
      cell.addEventListener('click',(function(el,r,c,i){ 
       return function(){ 
        callback(el,r,c,i); 
       } 
      })(cell,r,c,i),false); 
     } 
    } 
    return grid; 
} 

回答

0

OK,這一部分:

cell.addEventListener('click',(function(el,r,c,i){ 
    return function(){ 
     callback(el,r,c,i); 
    } 
})(cell,r,c,i),false); 

基本上是這樣做的:

cell.addEventListener('click',eventHandler,false); 

所以不管它取代了事件處理程序做的一部分,它必須返回一個函數作爲事件處理程序。所以爲了清晰起見,我們重寫:

var eventHandler = (function(el,r,c,i){ 
    return function(){ 
     callback(el,r,c,i); 
    } 
})(cell,r,c,i); 
cell.addEventListener('click',eventHandler,false); 

現在,eventHandler位是一個自調用函數。此外,它是一個功能工廠(它返回一個功能)。所以我們稱之爲eventHandlerFactory。現在爲了清晰起見將其改寫爲:

function eventHandlerFactory (el,r,c,i) { 
    return function(){ 
     callback(el,r,c,i); 
    } 
} 
var eventHandler = eventHandlerFactory(cell,r,c,i); 
cell.addEventListener('click',eventHandler,false); 

現在這樣更容易閱讀。eventHandlerFactory返回一個匿名函數,然後將其分配給eventHandler(暫時忽略該匿名函數的內容)。 eventHandlerFactory生成的eventHandler函數被分配給單元格的onclick事件。當單擊單元格時,eventHandler函數將被執行。 eventHandler函數的內容(現在可以停止忽略它)只是對前面提供的回調函數的調用。

問題是,爲什麼將函數工廠的另一層中的eventHandler包裝起來增加了複雜性?答案是循環問題中的經典閉包。變量r,c,icell被捕獲爲循環中的閉包,這是一個問題,因爲它會使所有單元共享相同的變量。在javascript中打破關閉的唯一方法是將您的引用作爲函數參數傳遞。所以額外的功能工廠在那裏打破關閉,從而確保每個單元格操作變量的正確值。

0
cell.addEventListener('click',(function(el,r,c,i){ 
       return function(){ 
        callback(el,r,c,i); 
       } 
      })(cell,r,c,i),false); 

JavaScript變數函數scoped.IE他的代碼是一樣的寫作。

function clickableGrid(rows, cols, callback){ 
    var i,grid,r,c,tr,cell; 
    ... 

爲了保留與一個需要創建的另一功能的循環的每次迭代的每個變量的值scope.Therefore EL,R,C,I被正確通過創建一個閉合作用域。

如果他沒有寫這個代碼。所有的回調函數都有相同的cell,r,c,i值,也就是2個循環最後一次迭代的值。

試試,除去立刻調用函數

cell.addEventListener('click', function(){ 
        callback(cell,r,c,i); 
       } 
      ,false); 

所有點擊只會僅通過回調的最後一個單元,最後一個R,C,I值時,所有的循環完成。

這與寫作時完全相同。

var x=5; 

window.getX=function(){ 
    return x; 
} 

x = 6; 

console.log(getX()) // 6 

x裏面的getX函數將始終引用x的最後一個值。

但如果我寫

var x=5; 

(function(x){ 

window.getX=function(){ 
    return x; 
} 

}(x)) 

x = 6; 

console.log(getX()) // 5 

中的x的getX指中的x立刻調用的功能表達(IIFE)的關閉。

還有很多,相信我比起初看起來要複雜得多。抓住「JavascriptAllongé」這本書來獲得全貌。

0

OK,所以這是最有趣的部分:

cell.addEventListener('click', 
    (function(el,r,c,i){ 
     return function(){ 
      callback(el,r,c,i); 
     } 
    })(cell,r,c,i), 
    false); 

更幼稚的做法是這樣的:

cell.addEventListener('click', 
    function(){ 
     callback(cell,r,c,i); 
    }, 
false); 

如果你嘗試了這一點,它總是會說:「你點擊這是因爲變量cell,r,c,i被捕獲他們自己在關閉而不是副本。也就是說,當它們的值在下一個循環中變化時,用於所有回調的值將改變。

這可以通過將閉包封裝在一個函數中來避免,該函數將所述變量作爲參數並立即執行,從而有效地創建新的局部變量。

圖案

(function(args){ 
    … 
})(stuff); 

在JS常用於內創建一個新的變量範圍。