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.


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ż:


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);
}


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);
    }
});
@*/

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);

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);
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.