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?


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


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

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

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.


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


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

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.


Duży problem: wszystkie instancje klasy mają takie same _elfy, więc to nie działa.
Astra Bear
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.