web-dev-qa-db-fra.com

Typescript "this" dans une méthode de classe

Je sais que c'est probablement douloureusement basique, mais j'ai du mal à comprendre.

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

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

}

Il semble que j’ai besoin d’un proxy, disons donc avec Jquery

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

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

}

Mais venant d'un contexte Actionscript 3, je ne suis pas vraiment sûr de ce qui se passe ici. Désolé, je ne suis pas sûr de l'endroit où commence Javascript et où se termine TypeScript.

updateProxy: () => void

Et aussi, je ne suis pas convaincu que je le fais bien. La dernière chose que je souhaite, c’est que la plupart des élèves de ma classe aient une fonction a()) à laquelle il faut accéder avec aProxy(), car j’ai l’impression d’écrire deux fois la même chose. c'est normal?

71
Clark

Si vous voulez capturer this, la méthode TypeScript consiste à utiliser des fonctions de flèche. Pour citer Anders:

La fonction this dans les flèches a une portée lexicale

Voici comment j'aime utiliser ceci à mon avantage:

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

Voir ceci dans le TypeScript Playground

Vous pouvez voir que dans le code JavaScript généré, this est capturé en dehors de de l'appel de fonction:

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

de sorte que la valeur this à l'intérieur de l'appel de fonction (qui pourrait être window) ne serait pas utilisée.

Pour en savoir plus: “Comprendre this dans TypeScript” (4:05) - YouTube

100
basarat

Si vous écrivez vos méthodes de la sorte, "ceci" sera traité comme vous le souhaitez.

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

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

Une autre option serait de lier 'this' à l'appel de fonction:

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

    update(): void
    {
        requestAnimationFrame(this.update.bind(this));
    }
}
17
joelnet

Voir page 72 de la spécification du langage TypeScript https://github.com/Microsoft/TypeScript/blob/master/doc/TypeScript%20Language%20Specification.pdf?raw=true

Expressions de fonctions fléchées

Dans l'exemple

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

l'utilisation d'une expression de fonction de flèche fait que le rappel a la même chose que la méthode ‘start’ environnante. En écrivant le callback en tant qu’expression de fonction standard, il devient nécessaire d’organiser manuellement l’accès à l’environnement environnant, par exemple en le copiant dans une variable locale:

Voici le code Javascript généré:

class Messenger {
 message = "Hello World";
 start() {
 var _this = this;
 setTimeout(function() { alert(_this.message); }, 3000);
 }
};
4
Simon_Weaver

Le problème se pose lorsque vous transmettez une fonction en tant que rappel. Au moment où le rappel a été exécuté, la valeur de "this" aurait pu être remplacée par la fenêtre, le contrôle invoquant le rappel ou autre chose.

Assurez-vous de toujours utiliser une expression lambda au moment où vous passez une référence à la fonction à rappeler. Par exemple

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

Cela compile à quelque chose comme

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

Comme la fonction addFile est appelée sur une référence d'objet spécifique (_this), elle n'utilise pas le "this" de l'appelant, mais la valeur de _this.

4
Peter Morris

En bref, le mot-clé this a toujours une référence à l'objet qui a appelé la fonction.

En Javascript, les fonctions n'étant que des variables, vous pouvez les faire circuler.

Exemple:

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

Lorsque vous effectuez les opérations suivantes avec jQuery:

$.proxy(this.update, this);

Ce que vous faites est de surcharger ce contexte. Dans les coulisses, jQuery vous le garantira:

$.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
  }
};
2
Kenneth

Très tard à la fête, mais je pense qu'il est très important que les futurs visiteurs de cette question tiennent compte de ce qui suit:

Les autres réponses, y compris celle acceptée, passent à côté d'un point crucial:

myFunction() { ... }

et

myFunction = () => { ... }

sont pas la même chose "à l'exception que ce dernier capture this".

La première syntaxe crée une méthode sur le prototype, tandis que la seconde syntaxe crée une propriété sur l'objet dont la valeur est une fonction (qui capture également this). Vous pouvez le voir clairement dans le JavaScript transpilé.

Pour être complet:

myFunction = function() { ... }

serait la même chose que la deuxième syntaxe, mais sans la capture.

Donc, en utilisant la syntaxe des flèches dans la plupart des cas résoudra votre problème de liaison à l'objet, mais ce n'est pas la même chose et il y a de nombreuses situations dans lesquelles vous voulez avoir une fonction appropriée sur le prototype au lieu d'une propriété.

Dans ces cas, utiliser un proxy ou .bind() en fait est la solution correcte. (Bien que souffrant de lisibilité.)

Plus de lecture ici (pas principalement à propos de TypeScript, mais les principes sont valables):

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

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

1
Karim Ayachi