Javascript setInterval und `this` Lösung

70

Ich muss thisvon meinem setIntervalHandler aus zugreifen

prefs: null,
startup : function()
    {
        // init prefs
        ...
        this.retrieve_rate();
        this.intervalID = setInterval(this.retrieve_rate, this.INTERVAL);
    },

retrieve_rate : function()
    {
        var ajax = null;
        ajax = new XMLHttpRequest();
        ajax.open('GET', 'http://xyz.com', true);
        ajax.onload = function()
        {
            // access prefs here
        }
    }

Wie kann ich auf this.prefs zugreifen ajax.onload?

Pablo
quelle

Antworten:

93

Die setInterval-Zeile sollte folgendermaßen aussehen: -

 this.intervalID = setInterval(
     (function(self) {         //Self-executing func which takes 'this' as self
         return function() {   //Return a function in the context of 'self'
             self.retrieve_rate(); //Thing you wanted to run as non-window 'this'
         }
     })(this),
     this.INTERVAL     //normal interval, 'this' scope not impacted here.
 ); 

Bearbeiten : Das gleiche Prinzip gilt für das " onload". In diesem Fall ist es üblich, dass der "äußere" Code wenig bewirkt. Er richtet lediglich die Anforderung ein und sendet sie dann. In diesem Fall ist der zusätzliche Aufwand für eine zusätzliche Funktion wie im obigen Code nicht erforderlich. Ihre retrieve_rate sollte ungefähr so ​​aussehen: -

retrieve_rate : function()
{
    var self = this;
    var ajax = new XMLHttpRequest();
    ajax.open('GET', 'http://xyz.com', true);
    ajax.onreadystatechanged= function()
    {
        if (ajax.readyState == 4 && ajax.status == 200)
        {
            // prefs available as self.prefs
        }
    }
    ajax.send(null);
}
AnthonyWJones
quelle
Ich wollte dies anfangs tun, aber dann erinnerte ich mich, dass dieses Muster für Schleifen wirklich am nützlichsten ist.
Matthew Flaschen
@Matthew Flaschen: Es ist für dieses Szenario genauso nützlich wie für Schleifen.
Andy E
@Anthony: Also ist der Trick mit selfder einzigen Option hier? Können Sie bestätigen, dass die Lösung von Matthew nicht funktioniert?
Pablo
@ Michael: Erstens ist es kein "Trick", sondern nur, wie die Dinge in Javascript funktionieren. Matthews Antwort, wie sie derzeit zum Zeitpunkt des Schreibens dieses Kommentars steht, funktioniert nicht. Es gab eine frühere Version davon, die möglicherweise funktioniert hat, aber die Übergabe thisals unnötiger und unangenehmer Parameter beinhaltete (jeder Aufrufer von retrieve_ratehätte diese unnötige spezielle Anforderung gekannt).
AnthonyWJones
1
Die Übergabe thisals Argument an (function(self){...})(this)in setInterval hat bei mir nicht funktioniert, da die Funktion sofort ausgeführt wird, anstatt verzögert zu werden. @ Joel Fillmores Lösung funktioniert für mich
Homan
92
this.intervalID = setInterval(this.retrieve_rate.bind(this), this.INTERVAL);
Nechehin
quelle
13
Dies ist die richtige Lösung. Die akzeptierte Lösung scheint unnötig mehr Code zu erfordern.
Nick
5
Diese Methode hat jedoch einen Nachteil. Höchstwahrscheinlich funktioniert es nicht mit älteren Versionen von IE
Nechehin
@Nechehin Bemerkenswert. Aber es ist immer noch eine viel sauberere Lösung.
Connorbode
Es wird seit IE9 unterstützt und ist daher eine saubere Lösung für mich.
barbara.post
2
Wenn Sie IE8 Unterstützung benötigen und Sie verwenden Underscore.js , könnten Sie verwenden _.bind:this.intervalID = setInterval(_.bind(this.retrieve_rate, this), this.INTERVAL);
gfullam
18

Das Standardverhalten von setIntervalist das Binden an den globalen Kontext. Sie können eine Mitgliedsfunktion aufrufen, indem Sie eine Kopie des aktuellen Kontexts speichern. Innerhalb von retrieve_rate wird die thisVariable korrekt an den ursprünglichen Kontext gebunden. So würde Ihr Code aussehen:

var self = this;
this.intervalID = setInterval(
    function() { self.retrieve_rate(); },
    this.INTERVAL);

Bonus-Tipp: Für eine einfache Funktionsreferenz (im Gegensatz zu einer Objektreferenz mit einer Elementfunktion) können Sie den Kontext mithilfe von JavaScript calloder applyMethoden ändern .

Joel Fillmore
quelle
1
Dies hat bei mir funktioniert, der Aufruf zum "Anrufen" scheint jedoch nicht erforderlich zu sein. Der Kontext von retrieve_rate sollte standardmäßig auf self gesetzt werden, da er als Member-Funktion aufgerufen wird.
Dreendle
@Dreendle - Sie haben Recht, ich erinnerte mich, dass ich dieses Problem für eine Rückruffunktionsreferenz gelöst habe, wo es benötigt wurde. Ich habe die Antwort korrigiert, danke!
Joel Fillmore
13

Mit Verbesserung der Browser - Unterstützung ist die Zeit nun gut , die verwenden EcmaScript 6 Erweiterung, auf die Pfeil - =>Methode , erhalten thisrichtig.

startup : function()
    {
        // init prefs
        ...
        this.retrieve_rate();
        this.intervalID = setInterval( () => this.retrieve_rate(), this.INTERVAL);
    },

Mit der Methode => wird das thisWann beibehalten, das retrieve_rate()vom Intervall aufgerufen wird. Keine Notwendigkeit für funky Selbst oder Übergabe thisvon Parametern

Martlark
quelle
9

window.setInterval(function(){console.log(this)}.bind(this), 100)

Dies ist legal in Javascript und spart viel Code :)

Daniel Apostolov
quelle
2

Dies wäre die sauberste Lösung, da Sie diesen Kontext die meiste Zeit tatsächlich für Ihre aufeinander folgenden Methodenaufrufe wechseln möchten:

Es ist auch einfacher, das Konzept von zu verstehen.

    // store scope reference for our delegating method
    var that = this;
    setInterval(function() {
        // this would be changed here because of method scope, 
        // but we still have a reference to that
        OURMETHODNAME.call(that);
    }, 200);
Dbl
quelle
1

Bei modernen Browsern ermöglicht die setInterval-Methode zusätzliche Parameter, die nach Ablauf des Timers an die von func angegebene Funktion übergeben werden.

var intervalID = scope.setInterval (func, delay [, param1, param2, ...]);

Daher kann eine mögliche Lösung sein:

this.intervalID = setInterval(function (self) {
        self.retrieve_rate();
    }, this.INTERVAL, this);

Eine Demo:

var timerId;
document.querySelector('#clickMe').addEventListener('click', function(e) {
    timerId = setInterval(function (self) {
        self.textContent = self.textContent.slice(0, -1);
        if (self.textContent.length == 0) {
            clearInterval(timerId);
            self.textContent = 'end..';
        }
    }, 250, this);
})
<button id="clickMe">ClickMe</button>

gaetanoM
quelle
0
prefs: null,
startup : function()
    {
        // init prefs
        ...
        this.retrieve_rate();
        var context = this;
        this.intervalID = setInterval(function()
                                      {
                                          context.retrieve_rate();
                                      }, this.INTERVAL);
    },

retrieve_rate : function()
    {
        var ajax = null;
        ajax = new XMLHttpRequest();
        ajax.open('GET', 'http://xyz.com', true);
        var context = this;
        ajax.onload = function()
        {
            // access prefs using context.
            // e.g. context.prefs
        }
    }
Matthew Flaschen
quelle
thisInnerhalb der an übergebenen Funktion setIntervalwird auf das globale Objekt verwiesen. Meinten Sie context.retrieve_ratestatt this.retrieve_rate?
Christian C. Salvadó
Dies hat sich in die richtige Richtung entwickelt. Der Kontext muss jedoch nicht als Parameter übergeben werden.
AnthonyWJones
@Matthew, Anthony Wie kann ich von Onload aus darauf zugreifen? Versuchte this.prefs, funktionierte aber nicht ...
Pablo
Sie sind willkommen , Matthew, nebenbei bemerkt , die Sie nicht verwenden müssen call, context.retrieve_rate()ist genug, da Sie eine haben Basisobjekt ( context.)
Christian C. Salvadó
Nochmals vielen Dank, @Anthony und @CMS. :) Es ist leicht, unnötig "klug" zu werden, wenn Schließungen im Spiel sind.
Matthew Flaschen
-1

Das ist keine Schönheitslösung, wird aber allgemein verwendet:

var self = this;
var ajax = null;
//...
ajax.onload = function() {
    self.prefs....;
}
Crozin
quelle
2
Das Problem ist, wie setIntervaldie retrieve_rateFunktion this
aufgerufen wird