javascriptで発生するイベントを間引く

近辺で話題になっていたのでまとめた。

イベントを律儀に全て処理しているとattachした即時関数が過剰に実行されてしまう。API通信が大量に発生したり、処理落ちが発生したりする。

lodashunderscoreに実装されている_.throttle_.debounceというファクトリを利用することで、これらをスマートに解決できる。

_.throttle

設定した時間内に2回以上実行されない関数を返すファクトリ。スクロールイベントのような連続量を間引くのに利用される。

$el = $ '#header'
($ window).on 'scroll', ->
  $el.css top: ($ document).scrollTop()

scrollに同期してヘッダを追従させたりする場合、scrollの全フレームを処理しなければいけないような気になる。

しかし、scrollイベントの全フレームを処理しないとカクカクして見えるほど人類のステージは高くない。

$el = $ '#header'
($ window).on 'scroll', _.throttle (event) ->
  $el.css top: ($ document).scrollTop()
, 12

これで「どれだけ実行しても12秒に1回しか実行できないようにラッピングされた」即時関数がscrollにattachされる。

何かをスクロールに同期する場合、12ミリ秒くらいだと殆ど気にならなかった、24くらいまで増やすと結構気になった。

「だいたい41ミリ秒で24fpsだから41でいいじゃん」と思ったけど元のscrollイベントがアナログ値ではないのでそんなことは全く無かった。

入ってきた連続量の頭を評価しない({ leading: false })、ケツを評価しない({ trailing: false })等のオプションがある。詳しくはドキュメントを参照の事。

_.debounce

実行するとタイマーをリセットしてabort、タイマーが切れると実行される関数を返すファクトリ。

($ '#input').on 'keyup', (event) ->
  $el = $ event.currentTarget
  $.ajax '/api/search',
    data: name: $el.val()
    dataType: 'json'
  .done (json) ->
    $el.val json.name if json.name

こんな即時関数を実装してAPIアクセスが大量に発生し、慌ててsetTimeoutで遅延実行するような機構を実装した経験が誰にでもあると思う。

でもdebounceがあればもう慌てる必要は無い。

($ '#input').on 'keyup', _.debounce (event) ->
  $el = $ event.currentTarget
  $.ajax '/api/search',
    data: name: $el.val()
    dataType: 'json'
  .done (json) ->
    $el.val json.name if json.name
, 240

これで「最後に実行されてから240ミリ秒以内にもう一度実行されたらabort、実行されなければexec」する即時関数がkeyupにattachされる。