TypeScript “this” problème de scope lorsqu’elle est appelée dans le callback jquery

Je ne suis pas sûr de la meilleure approche pour gérer la scope de “this” dans TypeScript.

Voici un exemple de modèle courant dans le code que je convertis en TypeScript:

class DemonstrateScopingProblems { private status = "blah"; public run() { alert(this.status); } } var thisTest = new DemonstrateScopingProblems(); // works as expected, displays "blah": thisTest.run(); // doesn't work; this is scoped to be the document so this.status is undefined: $(document).ready(thisTest.run); 

Maintenant, je pourrais changer l’appel à …

 $(document).ready(thisTest.run.bind(thisTest)); 

… qui fonctionne. Mais c’est un peu horrible. Cela signifie que le code peut tout comstackr et fonctionner correctement dans certaines circonstances, mais si nous oublions de lier la scope, il sera rompu.

Je voudrais un moyen de le faire dans la classe, de sorte que lors de l’utilisation de la classe, nous n’avons pas besoin de nous préoccuper de la scope de “this”.

Aucune suggestion?

Mettre à jour

Une autre approche qui fonctionne consiste à utiliser la grosse flèche:

 class DemonstrateScopingProblems { private status = "blah"; public run = () => { alert(this.status); } } 

Est-ce une approche valable?

    Vous avez quelques options ici, chacune avec ses propres compromis. Malheureusement, il n’y a pas de meilleure solution évidente et cela dépendra vraiment de l’application.

    Liaison de classe automatique
    Comme indiqué dans votre question:

     class DemonstrateScopingProblems { private status = "blah"; public run = () => { alert(this.status); } } 
    • Bon / mauvais: Cela crée une fermeture supplémentaire par méthode par instance de votre classe. Si cette méthode est généralement utilisée uniquement dans les appels de méthode classiques, cela est excessif. Cependant, s’il est beaucoup utilisé dans les positions de rappel, il est plus efficace pour l’instance de classe de capturer this contexte au lieu de chaque site d’appel créant une nouvelle fermeture lors de l’appel.
    • Bon: impossible pour les appelants externes d’oublier de gérer this contexte
    • Bon: Typesafe en TypeScript
    • Bon: Pas de travail supplémentaire si la fonction a des parameters
    • Mauvais: les classes dérivées ne peuvent pas appeler les méthodes de classe de base écrites de cette manière en utilisant super.
    • Mauvais: la sémantique exacte de ces méthodes est “pré-liée” et ne crée pas un contrat non-sécurisé supplémentaire entre votre classe et ses consommateurs.

    Function.bind
    Aussi comme indiqué:

     $(document).ready(thisTest.run.bind(thisTest)); 
    • Bon / mauvais: compromis mémoire / performance opposé par rapport à la première méthode
    • Bon: Pas de travail supplémentaire si la fonction a des parameters
    • Mauvais: dans TypeScript, cela n’a actuellement aucun type de sécurité
    • Mauvais: disponible uniquement dans ECMAScript 5, si cela vous concerne
    • Mauvais: vous devez taper deux fois le nom de l’instance

    Flèche de graisse
    Dans TypeScript (montré ici avec des parameters factices pour des raisons explicatives):

     $(document).ready((n, m) => thisTest.run(n, m)); 
    • Bon / mauvais: compromis mémoire / performance opposé par rapport à la première méthode
    • Bon: dans TypeScript, cela a une sécurité de type 100%
    • Bon: Fonctionne dans ECMAScript 3
    • Bon: il suffit de taper le nom de l’instance une fois
    • Mauvais: vous devrez taper deux fois les parameters
    • Mauvais: ne fonctionne pas avec les parameters variadiques

    Nécromancie.
    Il existe une solution simple évidente qui ne nécessite pas de fonctions fléchées (les fonctions fléchées sont 30% plus lentes), ou les méthodes JIT via les getters.
    Cette solution consiste à lier le contexte dans le constructeur.

     class DemonstrateScopingProblems { constructor() { this.run = this.run.bind(this); } private status = "blah"; public run() { alert(this.status); } } 

    Vous pouvez utiliser cette méthode pour lier automatiquement toutes les fonctions de la classe du constructeur:

     class DemonstrateScopingProblems { constructor() { this.autoBind(this); } [...] } export function autoBind(self: any) { for (const key of Object.getOwnPropertyNames(self.constructor.prototype)) { const val = self[key]; if (key !== 'constructor' && typeof val === 'function') { // console.log(key); self[key] = val.bind(self); } // End if (key !== 'constructor' && typeof val === 'function') } // Next key return self; } // End Function autoBind 

    Une autre solution nécessitant une configuration initiale mais payante avec sa légèreté invincible, littéralement, consiste à utiliser Method Decorators pour lier des méthodes via JIT.

    J’ai créé un repo sur GitHub pour présenter une implémentation de cette idée (il est un peu long de tenir dans une réponse avec ses 40 lignes de code, commentaires compris) , que vous utiliseriez aussi simplement que:

     class DemonstrateScopingProblems { private status = "blah"; @bound public run() { alert(this.status); } } 

    Je n’ai pas encore vu cela mentionné, mais cela fonctionne parfaitement. En outre, il n’ya pas d’inconvénient notable à cette approche: l’implémentation de ce décorateur – y compris une vérification de type pour la sécurité de type à l’exécution – est sortingviale et simple, et n’a pratiquement aucun coût après l’appel initial.

    La partie essentielle consiste à définir le getter suivant sur le prototype de classe, qui est exécuté immédiatement avant le premier appel:

     get: function () { // Create bound override on object instance. This will hide the original method on the prototype, and instead yield a bound version from the // instance itself. The original method will no longer be accessible. Inside a getter, 'this' will refer to the instance. var instance = this; Object.defineProperty(instance, propKey.toSsortingng(), { value: function () { // This is effectively a lightweight bind() that skips many (here unnecessary) checks found in native implementations. return originalMethod.apply(instance, arguments); } }); // The first invocation (per instance) will return the bound method from here. Subsequent calls will never reach this point, due to the way // JavaScript runtimes look up properties on objects; the bound method, defined on the instance, will effectively hide it. return instance[propKey]; } 

    Source complète


    L’idée peut également être franchie une étape supplémentaire, en effectuant cette opération dans un décorateur de classe, en parcourant les méthodes et en définissant le descripteur de propriété ci-dessus pour chacune d’entre elles en une seule fois.

    Dans votre code, avez-vous essayé de changer la dernière ligne comme suit?

     $(document).ready(() => thisTest.run());