Czy przekazać poprawny „ten” kontekst, aby ustawić wywołanie zwrotne setTimeout?

250

Jak przekazać kontekst setTimeout? Chcę zadzwonić, this.tip.destroy()jeśli this.options.destroyOnHidepo 1000 ms. Jak mogę to zrobić?

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

Kiedy próbuję powyższego, thisodnosi się do okna.

JamesBrownIsDead
źródło
4
Czy zduplikowana flaga jest naprawdę ważna? To pytanie zostało zadane wcześniej.
Sui Dream
1
if (this.options.destroyOnHide) {setTimeout (function () {this.tip.destroy ()} .bind (this), 1000); }
Zibri,

Odpowiedzi:

352

EDYCJA: Podsumowując, w 2010 r., Kiedy zadano to pytanie, najczęstszym sposobem rozwiązania tego problemu było zapisanie odwołania do kontekstu, w którym wywołano setTimeoutfunkcję, ponieważ setTimeoutwykonuje funkcję thiswskazując na obiekt globalny:

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

W specyfikacji ES5, wydanej przed rokiem, wprowadzono bindmetodę , która nie została zasugerowana w pierwotnej odpowiedzi, ponieważ nie była jeszcze szeroko obsługiwana i potrzebne były do ​​niej wielopełniacze, ale teraz jest ona wszędzie:

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

bindFunkcja tworzy nową funkcję o thiswartości wstępnie wypełnione.

Teraz we współczesnym JS jest to problem, który rozwiązują funkcje strzałek w ES6 :

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

Funkcje strzałek nie mają własnej thiswartości, gdy uzyskujesz do niej dostęp, uzyskujesz dostęp do thiswartości obejmującego zakresu leksykalnego.

HTML5 również znormalizował liczniki czasu w 2011 roku, a teraz możesz przekazać argumenty do funkcji wywołania zwrotnego:

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

Zobacz też:

CMS
źródło
3
To działa. Testowałem tę koncepcję za pomocą skryptu jsbin
John K
1
Kod ten polega na tworzeniu niepotrzebnej zmiennej (która ma zakres obejmujący całą funkcję); jeśli poprawnie przeszedłeś thisdo funkcji, rozwiązałeś ten problem dla tego przypadku, dla map (), forEach () itp., itp., używając mniej kodu, mniej cykli procesora i mniej pamięci. *** Patrz: odpowiedź Miszy Reyzlin.
HoldOffHunger,
222

Istnieją gotowe skróty (cukier składniowy) do opakowania funkcji @CMS, na które odpowiedziano. (Poniżej, zakładając, że kontekst, który chcesz, to this.tip.)


ECMAScript 5 ( bieżące przeglądarki , Node.js) i Prototype.js

Jeśli korzystasz z przeglądarki zgodnej z ECMA-262, wydanie 5 (ECMAScript 5) lub Node.js , możesz użyć Function.prototype.bind. Opcjonalnie możesz przekazać dowolne argumenty funkcji, aby utworzyć funkcje częściowe .

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

Ponownie w twoim przypadku spróbuj tego:

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

Ta sama funkcjonalność została również zaimplementowana w Prototype (jakieś inne biblioteki?).

Function.prototype.bindmożna zaimplementować w ten sposób, jeśli chcesz mieć niestandardową kompatybilność wsteczną (ale pamiętaj o uwagach).


ECMAScript 2015 ( niektóre przeglądarki , Node.js 5.0.0+)

Do najnowocześniejszego rozwoju (2015) możesz użyć funkcji grubych strzał , które są częścią specyfikacji ECMAScript 2015 (Harmony / ES6 / ES2015) ( przykłady ).

Ekspresja funkcji strzałka (znany także jako funkcję strzałki tłuszczu ) ma krótszy w porównaniu do składni wyrażeń funkcyjnych i leksykalnie wiąże thiswartość [...].

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

W twoim przypadku spróbuj tego:

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

jQuery

Jeśli już używasz jQuery 1.4+, istnieje gotowa funkcja do jawnego ustawienia this kontekstu funkcji.

jQuery.proxy () : Pobiera funkcję i zwraca nową, która zawsze będzie miała określony kontekst.

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

W twoim przypadku spróbuj tego:

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

Underscore.js , lodash

Jest dostępny w Underscore.js, a także lodash, jako _.bind(...)1 , 2

bind Powiązanie funkcji z obiektem, co oznacza, że ​​przy każdym wywołaniu funkcji wartościąthisbędzie obiekt. Opcjonalnie można powiązać argumenty z funkcją, aby je wstępnie wypełnić, znane również jako częściowe zastosowanie.

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

W twoim przypadku spróbuj tego:

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

Joel Purra
źródło
Dlaczego nie domyślne func.bind(context...)? Czy coś mi umknęło?
aTei,
Czy wydajne jest ciągłe tworzenie nowej funkcji (którą wykonuje bind) za każdym razem, gdy ją wywołujesz? Mam limit czasu wyszukiwania, który resetuje się po każdym naciśnięciu klawisza i wydaje się, że powinienem gdzieś buforować tę „powiązaną” metodę w celu ponownego użycia.
Triynko,
@Triynko: Nie widziałbym wiązania funkcji jako kosztownej operacji, ale jeśli wywołasz tę samą funkcję powiązaną wiele razy, równie dobrze możesz zachować odniesienie: var boundFn = fn.bind(this); boundFn(); boundFn();na przykład.
Joel Purra,
30

W przeglądarkach innych niż Internet Explorer możesz przekazać parametry do funkcji razem po opóźnieniu:

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

Możesz to zrobić:

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

Jest to lepsze pod względem wydajności niż wyszukiwanie zakresu (buforowanie thisdo zmiennej poza wyrażeniem limitu czasu / przedziału), a następnie tworzenie zamknięcia (przy użyciu $.proxylubFunction.prototype.bind ).

Kod umożliwiający jego działanie w IE z 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);
    }
});
@*/
Misza Reyzlin
źródło
1
Podczas tworzenia klasy przy użyciu łańcucha prototypów, a twoje metody są metodami prototypowymi ... „wiązanie” jest jedyną rzeczą, która zmieni to, co „to” jest w metodzie. Przekazywanie parametru do wywołania zwrotnego nie powoduje zmiany tego, co „to” jest w funkcji, więc takiej funkcji prototypowej nie można zapisać za pomocą „tego” w niej, jak każda inna metoda prototypowa. To prowadzi do niespójności. Wiązanie jest najbliższe temu, czego naprawdę chcemy, a zamknięcie można zapisać w pamięci podręcznej w „tym”, aby zwiększyć wydajność wyszukiwania i nie trzeba go tworzyć więcej niż raz.
Triynko,
3

UWAGA: To nie będzie działać w 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);
gumki
źródło
2

Jeśli używasz underscore, możesz użyćbind .

Na przykład

if (this.options.destroyOnHide) {
     setTimeout(_.bind(this.tip.destroy, this), 1000);
}
Sam
źródło