2013年6月21日金曜日

JavaScript でコールバックするときの注意

JavaScript では、this がどのオブジェクトを指しているのか注意が必要です。

例えば、クラスA が クラスB へ非同期処理をお願いして、終わったら渡しておいたコールバックを呼び出してもらうとします。
/**
 * クラスA
 * @class
 * @property {B} b クラスBのインスタンス
 */
var A = function() {
    this.myname = 'A';
    this.b = new B();
};

/**
 * クラスAの処理
 * B にコールバックを渡す
 * 非同期な処理が終わったらコールバックを呼び出してもらう
 * @method
 */
A.prototype.start = function() {
    this.b.start(this.callback);
};

/**
 * コールバック関数
 * @method
 */
A.prototype.callback = function() {
    alert('私の名前は ' + this.myname + ' です');
};

/**
 * クラスB
 * @class
 */
var B = function() {
}

/**
 * 非同期処理を行う
 * 終わったらコールバックを呼び出す
 * @method
 * @param {Function} callback コールバック関数
 */
B.prototype.start = function(callback) {
 setTimeout(function() {
  callback();
 });
};

// 実行
var a = new A();
a.start();    // "私の名前は undefined です"

this が a を指すと思いきや window を指します。
window.myname は未定義なので unndefined が出力されます。


こういう時は callback と一緒に自分自身を渡して、callback 呼び出し時に this として自分自身を指定してもらえば大丈夫です。
this を指定して関数を呼ぶときは call() を使います。
/**
 * クラスA
 * @class
 * @property {B} b クラスBのインスタンス
 */
var A = function() {
 this.myname = 'A';
    this.b = new B();
};

/**
 * クラスAの処理
 * B にコールバックを渡す
 * 非同期な処理が終わったらコールバックを呼び出してもらう
 * @method
 */
A.prototype.start = function() {
    this.b.start(this, this.callback);
};

/**
 * コールバック関数
 * @method
 */
A.prototype.callback = function() {
    alert('私の名前は ' + this.myname + ' です');
};

/**
 * クラスB
 * @class
 */
var B = function() {
}

/**
 * 非同期処理を行う
 * 終わったらコールバックを呼び出す
 * @method
 * @param {A} caller 呼び出し元のインスタンス
 * @param {Function} callback コールバック関数
 */
B.prototype.start = function(caller, callback) {
 setTimeout(function() {
  callback.call(caller);  // this = caller として callback() を実行
 });
};

// 実行
var a = new A();
a.start();    // "私の名前は A です"

0 件のコメント:

コメントを投稿