Passare il contesto "questo" corretto per impostare la richiamata Timeout?


249

Come passo il contesto setTimeout? Voglio chiamare this.tip.destroy()se this.options.destroyOnHidedopo 1000 ms. Come lo posso fare?

if (this.options.destroyOnHide) {
     setTimeout(function() { this.tip.destroy() }, 1000);
} 

Quando provo quanto sopra, si thisriferisce alla finestra.


4
Il flag duplicato è davvero valido? Questa domanda è stata effettivamente posta prima.
Sui Dream,

1
if (this.options.destroyOnHide) {setTimeout (function () {this.tip.destroy ()} .bind (this), 1000); }
Zibri

Risposte:


352

EDIT: In sintesi, nel 2010, quando è stata posta questa domanda, il modo più comune per risolvere questo problema era salvare un riferimento al contesto in cui setTimeoutviene effettuata la chiamata di funzione, poiché setTimeoutesegue la funzione thisindicando l'oggetto globale:

var that = this;
if (this.options.destroyOnHide) {
     setTimeout(function(){ that.tip.destroy() }, 1000);
} 

Nelle specifiche ES5, appena rilasciate un anno prima di allora, ha introdotto il bindmetodo , questo non è stato suggerito nella risposta originale perché non era ancora ampiamente supportato e avevi bisogno di polyfill per usarlo ma ora è ovunque:

if (this.options.destroyOnHide) {
     setTimeout(function(){ this.tip.destroy() }.bind(this), 1000);
}

La bindfunzione crea una nuova funzione con il thisvalore pre-riempito.

Ora nel moderno JS, questo è esattamente il problema che le funzioni freccia risolvono in ES6 :

if (this.options.destroyOnHide) {
     setTimeout(() => { this.tip.destroy() }, 1000);
}

Le funzioni freccia non hanno un thisvalore proprio, quando si accede ad esso, si accede al thisvalore dell'ambito lessicale allegato.

HTML5 ha anche standardizzato i timer nel 2011 e ora puoi passare argomenti alla funzione di callback:

if (this.options.destroyOnHide) {
     setTimeout(function(that){ that.tip.destroy() }, 1000, this);
}

Guarda anche:


3
Funziona. Ho testato il concetto con uno script jsbin
John K

1
Questo codice implica la creazione di una variabile non necessaria (che ha un ambito di applicazione); se si passasse correttamente thisalla funzione, si sarebbe risolto questo problema in questo caso, per map (), forEach (), ecc. ecc., utilizzando meno codice, meno cicli della CPU e meno memoria. *** Vedi: la risposta di Misha Reyzlin.
HoldOffHunger,

222

Esistono scorciatoie già pronte (zucchero sintattico) con cui ha risposto il wrapper funzione @CMS. (Di seguito supponiamo che il contesto desiderato sia this.tip.)


ECMAScript 5 ( browser correnti , Node.js) e Prototype.js

Se scegli come target un browser compatibile con ECMA-262, 5a edizione (ECMAScript 5) o Node.js , potresti utilizzare Function.prototype.bind. È possibile facoltativamente passare qualsiasi argomento di funzione per creare funzioni parziali .

fun.bind(thisArg[, arg1[, arg2[, ...]]])

Ancora una volta, nel tuo caso, prova questo:

if (this.options.destroyOnHide) {
    setTimeout(this.tip.destroy.bind(this.tip), 1000);
}

La stessa funzionalità è stata implementata anche in Prototype (altre librerie?).

Function.prototype.bindpuò essere implementato in questo modo se si desidera la compatibilità all'indietro personalizzata (ma si prega di osservare le note).


ECMAScript 2015 ( alcuni browser , Node.js 5.0.0+)

Per lo sviluppo all'avanguardia (2015) è possibile utilizzare le funzioni della freccia grassa , che fanno parte delle specifiche ECMAScript 2015 (Harmony / ES6 / ES2015) ( esempi ).

Un'espressione della funzione freccia (nota anche come funzione freccia grassa ) ha una sintassi più breve rispetto alle espressioni di funzione e associa lessicamente il thisvalore [...].

(param1, param2, ...rest) => { statements }

Nel tuo caso, prova questo:

if (this.options.destroyOnHide) {
    setTimeout(() => { this.tip.destroy(); }, 1000);
}

jQuery

Se stai già utilizzando jQuery 1.4+, esiste una funzione già pronta per l'impostazione esplicita del thiscontesto di una funzione.

jQuery.proxy () : accetta una funzione e ne restituisce una nuova che avrà sempre un contesto particolare.

$.proxy(function, context[, additionalArguments])

Nel tuo caso, prova questo:

if (this.options.destroyOnHide) {
    setTimeout($.proxy(this.tip.destroy, this.tip), 1000);
}

Underscore.js , lodash

È disponibile in Underscore.js, oltre a lodash, come _.bind(...)1 , 2

bind Associa una funzione a un oggetto, il che significa che ogni volta che viene chiamata la funzione, il valore dithissarà l'oggetto. Facoltativamente, associare gli argomenti alla funzione per precompilarli, noto anche come applicazione parziale.

_.bind(function, object, [*arguments])

Nel tuo caso, prova questo:

if (this.options.destroyOnHide) {
    setTimeout(_.bind(this.tip.destroy, this.tip), 1000);
}


Perché non predefinito func.bind(context...)? Mi manca qualcosa?
aTei il

È performante continuare a creare costantemente una nuova funzione (che legame fa) ogni volta che la chiami? Ho un timeout di ricerca che si reimposta dopo ogni pressione dei tasti e sembra proprio che dovrei memorizzare nella cache questo metodo "associato" da qualche parte per il riutilizzo.
Triynko,

@Triynko: Non vedrei l'associazione di una funzione come un'operazione costosa, ma se chiami la stessa funzione associata più volte potresti anche mantenere un riferimento: var boundFn = fn.bind(this); boundFn(); boundFn();ad esempio.
Joel Purra,

30

In browser diversi da Internet Explorer, è possibile passare i parametri alla funzione insieme dopo il ritardo:

var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);

Quindi, puoi farlo:

var timeoutID = window.setTimeout(function (self) {
  console.log(self); 
}, 500, this);

Ciò è migliore in termini di prestazioni rispetto a una ricerca dell'ambito (memorizzazione thisnella cache di una variabile al di fuori dell'espressione timeout / intervallo) e quindi creazione di una chiusura (utilizzando $.proxyo Function.prototype.bind).

Il codice per farlo funzionare in IE da Webreflection :

/*@cc_on
(function (modifierFn) {
  // you have to invoke it as `window`'s property so, `window.setTimeout`
  window.setTimeout = modifierFn(window.setTimeout);
  window.setInterval = modifierFn(window.setInterval);
})(function (originalTimerFn) {
    return function (callback, timeout){
      var args = [].slice.call(arguments, 2);
      return originalTimerFn(function () { 
        callback.apply(this, args) 
      }, timeout);
    }
});
@*/

1
Quando crei una classe usando la catena prototipo e i tuoi metodi sono metodi prototipo ... 'bind' è l'unica cosa che cambierà ciò che 'this' è all'interno del metodo. Passando un parametro al callback, non si modifica ciò che 'this' è nella funzione, quindi una tale funzione prototipo non può essere scritta usando 'this' al suo interno come qualsiasi altro metodo prototipo potrebbe. Ciò porta a un'incoerenza. Il bind è la cosa più vicina a ciò che realmente desideriamo e la chiusura potrebbe essere memorizzata nella cache in "this" per prestazioni di ricerca più elevate e non doverle creare più di una volta.
Triynko,

3

NOTA: questo non funzionerà in IE

var ob = {
    p: "ob.p"
}

var p = "window.p";

setTimeout(function(){
    console.log(this.p); // will print "window.p"
},1000); 

setTimeout(function(){
    console.log(this.p); // will print "ob.p"
}.bind(ob),1000);

2

Se stai usando underscore, puoi usarebind .

Per esempio

if (this.options.destroyOnHide) {
     setTimeout(_.bind(this.tip.destroy, this), 1000);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.