JavaScriptでコールバック関数を使うことがよくある。
コールバック関数の中で、コールバック関数の外側の値を参照したいこともよくある。

参照する「コールバック関数の外側の値」にはいくつかのパターンがある。
どうコードを書けばどの値を参照できるかすぐ忘れるので、メモしておく。

  1. コールバックが定義された時点の値を参照する
    • クロージャを使って値を保存しておく
  2. コールバックが呼ばれた時点での、コールバックが定義された箇所の値を参照する
    • シンプルに変数を参照するだけ
  3. コールバックが呼ばれた時点での、イベントが発生した箇所の値を参照する
    • イベントの発生源で値を渡す(他に方法あるのかな…)

テストコード

node.jsでやる。
Events

ブラウザでやるなら、addEventLister/dispatchEventとか使うのがいいと思う。
Creating custom events

例 1

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();

(()=>{
  // 「コールバックが定義された時点の値」
  var x = 'function-define-1';
  // 1. コールバックが定義された時点の値を参照する
  myEmitter.on('event-closure', (() => {
    var inner_x = x;
    return () => {
      console.log('closure-x: ' + inner_x);
    };
  })());
  // 2. コールバックが呼ばれた時点での、コールバックが定義された箇所の値を参照する
  myEmitter.on('event-arrowfunction', () => {
    console.log('arrowfunction-x: ' + x);
  });
  // 3. コールバックが呼ばれた時点での、イベントが発生した箇所の値を参照する
  myEmitter.on('event-arrowfunction-args', (x) => {
    console.log('arrowfunction-args-x: ' + x);
  });
  // 「コールバックが呼ばれた時点での、コールバックが定義された箇所の値」
  var x = 'function-define-2';
})();

(()=>{
  // 「コールバックが呼ばれた時点での、イベントが発生した箇所の値」
  var x = 'function-call';
  // イベントを発生させる。
  myEmitter.emit('event-closure');
  myEmitter.emit('event-arrowfunction');
  myEmitter.emit('event-arrowfunction-args', x);
})();

実行結果。

closure-x: function-define-1
arrowfunction-x: function-define-2
arrowfunction-args-x: function-call

例 2

もうちょっと実践的?な例は下記。

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();

(()=>{
  for(var i=0; i<2; i++){
    // 1. コールバックが定義された時点の値を参照する
    myEmitter.on('event-closure-'+i, (() => {
      var inner_i = i;
      return () => console.log('event-closure-i: ' + inner_i);
    })());
    // 2. コールバックが呼ばれた時点での、コールバックが定義された箇所の値を参照する
    myEmitter.on('event-arrowfunction-'+i, () => {
      console.log('event-arrowfunction-i: ' + i);
    });
  }
})();

(()=>{
  for(var i=0; i<2; i++){
    console.log('i: ' + i);
    myEmitter.emit('event-closure-'+i);
    myEmitter.emit('event-arrowfunction-'+i);
  }
})();

実行結果。

i: 0
event-closure-i: 0
event-arrowfunction-i: 2
i: 1
event-closure-i: 1
event-arrowfunction-i: 2