2013-05-16 21 views
2

我想了解bacon.js和FRP,所以試圖做一個簡單的拖放示例,但我遇到了懶惰評估的一段代碼。當我在視頻流中添加.log()時,它看起來很好看,但如果我將它取出,它不會更新。下面是我在做什麼:Bacon.js懶惰的評價,鼠標拖動示例中斷沒有日誌()語句

// UI streams 
block_mousedown = block_el.asEventStream('mousedown').map(xyFromEvent); 
global_mousemove = html.asEventStream('mousemove').map(xyFromEvent); 
global_mouseup = html.asEventStream('mouseup'); 

// Composites 
isDragging = block_mousedown.merge(global_mouseup.map(0)); 
mouseDragging = Bacon.combineAsArray(isDragging, global_mousemove) 
    .filter(function(v){ return notZero(v[0]) }) 

mouseDeltaFromClick = mouseDragging 
    .map(getDelta) 

// Block offset when it was clicked on 
block_pos_at_mousedown = block_mousedown 
    .map(function(a,b){ return block_el.offset();}) 
    .map(function(e){ return [e.left, e.top]; }) 
    // If I remove this log(), it doesn't evaluate 
    .log(); 

// merge mouse delta with block position when clicked  
mouseDeltaAndBlockPos = mouseDeltaFromClick 
    .combine(block_pos_at_mousedown, '.concat') 
    .onValue(function(e){ 
     block_el.css({ 
      top : e[3]+e[1]+"px", 
      left : e[2]+e[0]+"px" 
     }); 
    });  

這裏是一個jsFiddle of it

我想我可能會對此都錯了,這是甚至是正確的做法?我想在點擊時通過塊的位置,它應該在mousedown上更新,但不會與mousemove一起更新。

回答

2

您所描述的行爲與惰性評估無關:問題的根源在於執行順序。

在你的代碼(不log()block_pos_at_mousedownmouseDeltaFromClick似乎改變之前block_pos_at_mousedown(我必須說,我不知道log()究竟是如何變化的順序)。讓我們保持這一點。

observable.combine方法預計Property作爲第一個參數 - EventStream你通過時會自動轉換。現在,每當mouseDeltaFromClickblock_pos_at_mousedown更改時,mouseDeltaAndBlockPos更改(並因此觸發所有回調)。

所以,當block_pos_at_mousedown之前mouseDeltaFromClick火災回調在代碼結束時調用新的增量但與舊的塊位置(因爲back_pos_at_mousedown轉化成Property)。舊值爲[0,0],所以塊在每次點擊時捕捉到左上角。

如何解決?安全的方法是不要假設不相關回調的執行順序,並在考慮到這一點時重新編寫它。我想出了這個:

function xyFromEvent(v){ return [v.clientX, v.clientY]; } 

function getDelta(t){ 
    var a = t[1]; 
    var b = t[0]; 
    return [a[0]-b[0], a[1]-b[1]]; 
} 

function add(p1, p2) { 
    return [p1[0] + p2[0], p1[1] + p2[1]]; 
} 

$().ready(function() { 
    var block = $("#clickable-block"); 
    var html = $("html"); 

    var blockDragging = block.asEventStream('mousedown').map(true) 
          .merge(html.asEventStream('mouseup').map(false)) 
          .toProperty(false); 

    var deltas = html.asEventStream('mousemove').map(xyFromEvent).slidingWindow(2,2).map(getDelta); 

    // Just like deltas, but [0,0] when user is not dragging the box. 
    var draggingDeltas = Bacon.combineWith(function(delta, dragging) { 
     if(!dragging) { 
      return [0, 0]; 
     } 
     return delta; 
    }, deltas, blockDragging); 

    var blockPosition = draggingDeltas.scan([0,0], add); 

    blockPosition.onValue(function(pos) { 
     block.css({ 
      top : pos[1] + "px", 
      left : pos[0] + "px" 
     }); 
    }); 
}); 

而且的jsfiddle:http://jsfiddle.net/aknNh/25/

編輯:在評論raimohanska曾建議使用flatMap另一種解決方案:http://jsfiddle.net/TFPge/1/

+0

懶惰的評價和評估的順序齊頭並進。 .. –

+0

我同意懶惰評估會導致意外的行爲,如果您的代碼依賴於在特定時間被調用的* map *函數。盧卡斯的回答對我來說仍然很不錯。除非我使用過flatMap,如Wolfflow的繪圖示例中所示:http://codepen.io/wolfflow/pen/cudiJ Google Groups中還有一個相關的主題:https://groups.google.com/forum/ #!topic/baconjs/OR0C2jPCF9g – raimohanska

+0

這裏是我的小提琴與flatMap:http://jsfiddle.net/TFPge/ – raimohanska