web-dev-qa-db-ja.com

Javascriptクロージャと「これ」

作成したオブジェクトに問題があり、次のようになります。

var myObject = {

    AddChildRowEvents: function(row, p2) {
        if(document.attachEvent) {
            row.attachEvent('onclick', function(){this.DoSomething();});
        } else {
            row.addEventListener('click', function(){this.DoSomething();}, false);
        }
    },

    DoSomething: function() {
        this.SomethingElse(); //<-- Error here, object 'this' does not support this method.
    }
}

問題は、「DoSomething」関数内にいるときに、「this」が「myObject」を参照していないことです。何が間違っているのでしょうか。

35
Chris

関数が呼び出されるとき、「これ」は行を指します。オブジェクトが必要な場合は、次のように実行できます。]

AddChildRowEvents: function(row, p2) {
    var theObj = this;
    if(document.attachEvent) {
         row.attachEvent('onclick', function(){theObj.DoSomething();});
    } else {
         row.addEventListener('click', function(){theObj.DoSomething();}, false);
    }
},

関数が呼び出されると、関数が定義されたときにスコープ内にあった変数theOBjにアクセスできます。

36
Mike Kantor

thisは常に内部関数を参照します。ネストされた関数がある場合は、別の変数を作成し、それをthisにポイントする必要があります。

var myObject = {
    AddChildRowEvents: function(row, p2) {
        var that = this;
        if(document.attachEvent) {
            row.attachEvent('onclick', function(){that.DoSomething();});
        } else {
            row.addEventListener('click', function(){that.DoSomething();}, false);
        }
    }
}
10
svinto

問題は、JSでthisがどのように管理されているかに起因します。特に、コールバックが非同期で呼び出された場合(およびクロージャが作成された場合は、これにバインドされます)、迅速に処理できます。

AddChildRowEventsメソッドが呼び出されると、thisは変数myObjectを適切に参照します。ただし、このメソッドの目的は、クリック用のハンドラーを配置することです。そのため、イベントは将来いつかトリガーされます。そのとき、どのオブジェクトが実際にイベントをトリガーしますか?実際、ユーザーがクリックするのはDOM elementです。したがって、thisはこのDOM elementを参照し、myObject変数は参照しません。

提示されているものよりも最新のソリューションについては、bind methodまたはarrow functionを使用できます。

1.矢印機能を使用したソリューション

let handler = {
        addEventHandler: function(row) {
          console.log("this", this)
        row.addEventListener("click", () => {
        console.log("this", this)
        this.doSomethingElse()
    })
  },
  
  doSomethingElse: function() {
        console.log("something else")
  }
}
var div = document.querySelector("div")
handler.addEventHandler(div)
<div>one</div>

arrow functionES2015以降で使用可能)を使用すると、実行コンテキストではなく字句コンテキストでthisコンテキストが使用されます。 2つの違いは、字句コンテキストは実行時ではなくビルド時に検出されるため、字句コンテキストの追跡が容易になることです。ここで、arrow function内では、thisは外部関数の同じthis(つまり、addEventHandler)を参照するため、myObjectを参照します。

ファットアロー関数と通常の関数のプロパティの詳細については、次を参照してください。 https://dmitripavlutin.com/6-ways-to-declare-javascript-functions/

2.バインドを使用したソリューション

let handler = {
        addEventHandler: function(row) {
          console.log("this", this)
        row.addEventListener("click", function() {
        console.log("this", this)
        this.doSomethingElse()
    }.bind(this))
  },
  
  doSomethingElse: function() {
        console.log("something else")
  }
}
var div = document.querySelector("div")
handler.addEventHandler(div)
<div>one</div>

今回、コールバック関数がaddEventListenerに渡されると、thisコンテキストが外部のthis関数のaddEventHandler要素にバインドされます。

4
pom421

これはクロージャの一般的な問題です。それを解決するには、次のようなものを試してください。

var myObject = {    
    AddChildRowEvents: function(row, p2) { 
        var self = this;

        if(document.attachEvent) {            
             row.attachEvent('onclick', function(){this.DoSomething(self);});        
        } else {            
             row.addEventListener('click', function(){this.DoSomething(self);}, false);        
        }    
    },    

    DoSomething: function(self) {       
        self.SomethingElse(); 
    }
}
3
Chris Schick

コードの問題はここにあります。次のコード行で無名関数を定義し、それをonClickイベントのイベントハンドラーとして次の行に渡しました。

    row.attachEvent('onclick', function(){this.DoSomething();});

そして

    row.addEventListener('click', function(){this.DoSomething();}, false);

onclickイベントが発生し、渡した匿名関数が呼び出されると、thisコンテキストはイベントが発生したオブジェクトを参照しますが、myObjectは参照しません。この時点で、実行中のコンテキストはイベントハンドラーコンテキストにあるためです。

解決策:Mike Kantorに言われたトリックを実行するか、jQueryを使用して、プロキシメソッドを使用して無名関数でthisコンテキストを定義できます。

したがって、コードは次のようになります。

var myObject = {

    AddChildRowEvents: function(row, p2) {
        if(document.attachEvent) {
            row.attachEvent('onclick', $.proxy(function () {
                this.DoSomething();
            }, this));
        } else {
            row.addEventListener('click', $.proxy(function () {
                this.DoSomething();
            },this), false);
        }
    },

    DoSomething: function() {
        this.SomethingElse(); //<-- Error here, object 'this' does not support this method.
    }
}

エラーがthis.SomthingElse()にあるとおっしゃいましたが、コードに表示されません。そのコード行で実際にエラーが発生した場合は、メソッドDoSomthingをイベントハンドラーとして使用している可能性があります。

2
Farshid Saberi