web-dev-qa-db-ja.com

JavaScriptコールバックスコープ

コールバック関数でオブジェクトを参照する際に、単純な古いJavaScript(フレームワークなし)で問題が発生しています。

function foo(id) {
    this.dom = document.getElementById(id);
    this.bar = 5;
    var self = this;
    this.dom.addEventListener("click", self.onclick, false);
}

foo.prototype = {
    onclick : function() {
        this.bar = 7;
    }
};

新しいオブジェクトを作成するとき(DOMが読み込まれた後、span#testで)

var x = new foo('test');

Onclick関数内の「this」は、fooオブジェクトではなくspan#testを指します。

Onclick関数内でfooオブジェクトへの参照を取得するにはどうすればよいですか?

58
Chris MacDonald

(他の回答のコメントに隠されていた説明を抽出)

問題は次の行にあります。

this.dom.addEventListener("click", self.onclick, false);

ここでは、コールバックとして使用される関数オブジェクトを渡します。イベントがトリガーされると、関数が呼び出されますが、オブジェクト(this)との関連付けはありません。

この問題は、次のようにクロージャで関数を(オブジェクト参照で)ラップすることで解決できます。

this.dom.addEventListener(
  "click",
  function(event) {self.onclick(event)},
  false);

変数selfはクロージャーの作成時に割り当てられたthisであるため、クロージャー関数は後で呼び出されたときにself変数の値を記憶します。

これを解決する別の方法は、ユーティリティ関数を作成することです(バインドに変数を使用しないようにしますthis):

function bind(scope, fn) {
    return function () {
        fn.apply(scope, arguments);
    };
}

更新されたコードは次のようになります。

this.dom.addEventListener("click", bind(this, this.onclick), false);

Function.prototype.bind はECMAScript 5の一部であり、同じ機能を提供します。だからあなたができる:

this.dom.addEventListener("click", this.onclick.bind(this), false);

ES5をまだサポートしていないブラウザーの場合、 MDNは次のシムを提供します

if (!Function.prototype.bind) {  
  Function.prototype.bind = function (oThis) {  
    if (typeof this !== "function") {  
      // closest thing possible to the ECMAScript 5 internal IsCallable function  
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");  
    }  

    var aArgs = Array.prototype.slice.call(arguments, 1),   
        fToBind = this,   
        fNOP = function () {},  
        fBound = function () {  
          return fToBind.apply(this instanceof fNOP  
                                 ? this  
                                 : oThis || window,  
                               aArgs.concat(Array.prototype.slice.call(arguments)));  
        };  

    fNOP.prototype = this.prototype;  
    fBound.prototype = new fNOP();  

    return fBound;  
  };  
} 
81
hishadow
this.dom.addEventListener("click", function(event) {
    self.onclick(event)
}, false);
15
Sergey Ilinsky

この問題の解決策を探しているjQueryユーザーの場合、 jQuery.proxy を使用する必要があります。

5
Seldaek

説明は、self.onclickは、JavaScriptであなたが意味すると思うことを意味しません。実際には、オブジェクトonclickのプロトタイプ内のself関数を意味します(self自体を参照することは一切ありません)。

JavaScriptには関数のみがあり、C#のようなデリゲートはありません。そのため、メソッドとコールバックとして適用されるオブジェクトを渡すことはできません。

コールバックでメソッドを呼び出す唯一の方法は、コールバック関数内で自分で呼び出すことです。 JavaScript関数はクロージャーであるため、作成されたスコープで宣言された変数にアクセスできます。

var obj = ...;
function callback(){ return obj.method() };
something.bind(callback);
2
Vincent Robert

問題の適切な説明(これまで説明したソリューションの理解に問題がありました)は こちらから入手可能 です。

2
tpk

このプラグインを書きました...

役に立つと思う

jquery.callback

1
alberto