Maszynopis „this” wewnątrz metody klasy

84

Wiem, że jest to prawdopodobnie boleśnie podstawowe, ale ciężko mi to obejść.

class Main
{
     constructor()
     {
         requestAnimationFrame(this.update);  //fine    
     }

     update(): void
     {
         requestAnimationFrame(this.update);  //error, because this is window
     }

}

Wygląda na to, że potrzebuję proxy, więc powiedzmy, że używam Jquery

class Main
{
     constructor()
     {
         this.updateProxy = $.proxy(this.update, this);
         requestAnimationFrame(this.updateProxy);  //fine    
     }

     updateProxy: () => void
     update(): void
     {
         requestAnimationFrame(this.updateProxy);  //fine
     }

}

Ale wychodząc z tła Actionscript 3, nie jestem pewien, co się tutaj dzieje. Przepraszam, nie jestem pewien, gdzie zaczyna się JavaScript, a kończy TypeScript.

updateProxy: () => void

Nie jestem też przekonany, że robię to dobrze. Ostatnią rzeczą, której chcę, jest to, że większość mojej klasy ma funkcję aa (), do której należy uzyskać dostęp, aProxy()ponieważ czuję, że piszę to samo dwa razy? Jest to normalne?

Clark
źródło
Uważam, że ta dokumentacja jest bardzo pomocna github.com/Microsoft/TypeScript/wiki/ ...
rainversion_3

Odpowiedzi:

119

Jeśli chcesz thisuchwycić sposób TypeScript, robi to za pomocą funkcji strzałkowych. Cytując Andersa:

Funkcje thisstrzałek in mają zasięg leksykalny

Oto sposób, w jaki lubię to wykorzystać na swoją korzyść:

class test{
    // Use arrow functions
    func1=(arg:string)=>{
            return arg+" yeah" + this.prop;
    }
    func2=(arg:number)=>{
            return arg+10 + this.prop;
    }       

    // some property on this
    prop = 10;      
}

Zobacz to w TypeScript Playground

Możesz zobaczyć, że w wygenerowanym skrypcie JavaScript thisjest przechwytywany poza wywołaniem funkcji:

var _this = this;
this.prop = 10;
this.func1 = function (arg) {
    return arg + " yeah" + _this.prop;
};

więc thiswartość wewnątrz wywołania funkcji (która mogłaby być window) nie byłaby używana.

Więcej informacji: „Understanding thisin TypeScript” (4:05) - YouTube

basarat
źródło
2
To nie jest konieczne. Sugerujesz idiomatyczny JavaScript, ale TypeScript sprawia, że ​​jest to niepotrzebne.
Tatiana Racheva
1
@TatianaRacheva używanie funkcji strzałek w kontekście członków klasy nie było dozwolone przed TS 0.9.1 (a ta odpowiedź była wcześniej). Zaktualizowana odpowiedź do nowej składni :)
basarat
19
MUSISZ OBEJRZEĆ FILM - BARDZO PRZYDATNE. TYLKO 5 MINUT
Simon_Weaver
1
Dziękuję @basarat. Żarówka udał się do kontekstu to tak szybko, jak widziałem użyć funkcji strzałka w połowie drogi przez wideo. Doceniam Cię.
Simon
1
@AaronLS na placu zabaw TypeScript do wygenerowania var _this = this;musisz wybrać ES5; ponieważ ES2015 - funkcje wewnętrzne - thisjest używany zamiast_this
Stefano Spinucci
19

Jeśli napiszesz swoje metody w ten sposób, „to” zostanie potraktowane tak, jak oczekujesz.

class Main
{
    constructor()
    {
        requestAnimationFrame(() => this.update());
    }

    update(): void
    {
        requestAnimationFrame(() => this.update());
    }
}

Inną opcją byłoby powiązanie „this” z wywołaniem funkcji:

class Main
{
    constructor()
    {
        requestAnimationFrame(this.update.bind(this));
    }

    update(): void
    {
        requestAnimationFrame(this.update.bind(this));
    }
}
joelnet
źródło
2
Z mojego doświadczenia wynika, że ​​funkcja aktualizacji jest lepiej zdefiniowana w ten sposób: update = () => {...}
Shaun Rowan
Dużo używałem maszynopisu i wiele razy zmieniałem się w tę iz powrotem. w tej chwili uwzględniam nawiasy klamrowe tylko wtedy, gdy jest to coś więcej niż proste wywołanie metody. również kiedy używasz maszynopisu + linq (co jest boskie), format jest ładniejszy. przykład: Enumerable.From (arr) .Where (o => o.id == 123);
joelnet
Myślę, że jeśli spojrzysz na wygenerowany javascript, zobaczysz znaczącą różnicę. To nie jest kwestia gustu. update = () => {} utworzy zakres leksykalny poprzez kompilację "var _this = this", twoja składnia nie.
Shaun Rowan
1
Konieczne może być uaktualnienie biblioteki TypeScript, ponieważ absolutnie zawiera ona kontekst „_this”. Używając "() => code ()" lub () => {return code (); .}”Wyjście będzie 100% identyczny kod javascript Oto wynik: i.imgur.com/I5J12GE.png Można również zobaczyć na własne oczy, wklejając kod do. Typescriptlang.org/Playground
joelnet
widocznie bind (to) może być zły, ponieważ traci typu bezpieczeństwa na oryginalnych args funkcyjnych
robert króla
6

Patrz strona 72 specyfikacji języka maszynopisu https://github.com/Microsoft/TypeScript/blob/master/doc/TypeScript%20Language%20Specification.pdf?raw=true

Wyrażenia funkcyjne strzałek

W przykładzie

class Messenger {
 message = "Hello World";
 start() {
 setTimeout(() => alert(this.message), 3000);
 }
};
var messenger = new Messenger();
messenger.start();

użycie wyrażenia funkcyjnego strzałki powoduje, że wywołanie zwrotne ma to samo, co otaczająca metoda „start”. Pisząc callback jako standardowe wyrażenie funkcyjne, konieczne staje się ręczne zaaranżowanie dostępu do otoczenia, na przykład poprzez skopiowanie go do zmiennej lokalnej:

To jest faktycznie wygenerowany JavaScript:

class Messenger {
 message = "Hello World";
 start() {
 var _this = this;
 setTimeout(function() { alert(_this.message); }, 3000);
 }
};
Simon_Weaver
źródło
Link jest niestety uszkodzony
Jimmy Kane
1
@JimmyKane Zaktualizowałem link. Co dziwne, ten dokument ma ponad rok i nadal zawiera odniesienia na ich stronie domowej, ale ważne treści, które zawarłem w odpowiedzi.
Simon_Weaver
4

Problem pojawia się, gdy przekazujesz funkcję jako wywołanie zwrotne. Zanim callback wykona, wartość „this” mogła zmienić się na Window, kontrolkę wywołującą callback lub coś innego.

Upewnij się, że zawsze używasz wyrażenia lambda w momencie przekazywania odniesienia do funkcji, która ma zostać wywołana z powrotem. Na przykład

public addFile(file) {
  this.files.push(file);
}
//Not like this
someObject.doSomething(addFile);
//but instead, like this
someObject.doSomething( (file) => addFile(file) );

To kompiluje się do czegoś podobnego

this.addFile(file) {
  this.files.push(file);
}
var _this = this;
someObject.doSomething(_this.addFile);

Ponieważ funkcja addFile jest wywoływana na podstawie określonego odwołania do obiektu (_this), nie używa ona wartości „this” obiektu wywołującego, ale zamiast tego wartość _this.

Peter Morris
źródło
Kiedy mówisz, do czego się składa, który z nich pokazujesz? (Instancja strzały czy ta, która po prostu przechodzi przez obiekt metody?)
Vaccano
Lambda. Po prostu utwórz TS z tym kodem i zobacz, co skompiluje.
Peter Morris
3

Bardzo późno na imprezę, ale myślę, że dla przyszłych odwiedzających to pytanie bardzo ważne jest rozważenie następujących kwestii:

Pozostałe odpowiedzi, w tym zaakceptowana, pomijają kluczowy punkt:

myFunction() { ... }

i

myFunction = () => { ... }

nie są tym samym, „z wyjątkiem tego, że ten ostatni przechwytuje this”.

Pierwsza składnia tworzy metodę na prototypie, podczas gdy druga składnia tworzy właściwość na obiekcie, którego wartość jest funkcją (która również jest przechwytywana this). Widać to wyraźnie w transpiled JavaScript.

Aby być kompletnym:

myFunction = function() { ... }

byłaby taka sama jak druga składnia, ale bez przechwytywania.

Tak więc, użycie składni strzałek w większości przypadków rozwiązuje problem z wiązaniem się z obiektem, ale to nie to samo i jest wiele sytuacji, w których chcesz mieć odpowiednią funkcję na prototypie zamiast właściwości.

W takich przypadkach użycie proxy lub .bind()faktycznie jest właściwym rozwiązaniem. (Chociaż cierpi na czytelność.)

Więcej do czytania tutaj (nie głównie o TypeScript, ale zasady obowiązują):

https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1

https://ponyfoo.com/articles/binding-methods-to-class-instance-objects

Karim Ayachi
źródło
2

Krótko mówiąc, słowo kluczowe this zawsze ma odniesienie do obiektu, który wywołał funkcję.

W JavaScript, ponieważ funkcje są tylko zmiennymi, możesz je przekazywać.

Przykład:

var x = {
   localvar: 5, 
   test: function(){
      alert(this.localvar);
   }
};

x.test() // outputs 5

var y;
y.somemethod = x.test; // assign the function test from x to the 'property' somemethod on y
y.test();              // outputs undefined, this now points to y and y has no localvar

y.localvar = "super dooper string";
y.test();              // outputs super dooper string

Gdy wykonasz następujące czynności z jQuery:

$.proxy(this.update, this);

To, co robisz, zastępuje ten kontekst. Za kulisami jQuery poprowadzi Cię w ten sposób:

$.proxy = function(fnc, scope){
  return function(){
     return fnc.apply(scope);  // apply is a method on a function that calls that function with a given this value
  }
};
Kenneth
źródło
1

Co powiesz na zrobienie tego w ten sposób? Zadeklaruj zmienną globalną typu „myClass” i zainicjalizuj ją w konstruktorze klasy:

var _self: myClass;

class myClass {
    classScopeVar: string = "hello";

    constructor() {
        _self = this;
    }

    alerter() {
        setTimeout(function () {
            alert(_self.classScopeVar)
        }, 500);
    }
}

var classInstance = new myClass();
classInstance.alerter();

Uwaga: Ważne jest, aby NIE używać słowa „ja”, ponieważ jest to już specjalne znaczenie.

Maxter
źródło
Duży problem: wszystkie instancje klasy mają takie same _elfy, więc to nie działa.
Astra Bear