Je ne suis pas sûr de la meilleure approche pour gérer la portée de "ceci" dans TypeScript.
Voici un exemple de motif commun 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 en ...
$(document).ready(thisTest.run.bind(thisTest));
... qui fonctionne. Mais c'est un peu horrible. Cela signifie que le code peut tout compiler et fonctionner correctement dans certaines circonstances, mais si nous oublions de lier la portée, il se cassera.
Je voudrais un moyen de le faire au sein de la classe, de sorte que, lors de l'utilisation de la classe, nous n'ayons pas à nous soucier de ce à quoi "ceci" est destiné.
Aucune suggestion?
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 valide?
Vous avez quelques options ici, chacune avec ses propres compromis. Malheureusement, il n'y a pas de solution évidente et dépendra vraiment de l'application.
Liaison de classe automatique
Comme le montre votre question:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
this
au lieu de chaque site d'appel créant une nouvelle fermeture à l'appel.this
super.
Function.bind
Également comme indiqué:
$(document).ready(thisTest.run.bind(thisTest));
grosse flèche
Sous TypeScript (indiqué ici avec quelques paramètres factices pour des raisons explicatives):
$(document).ready((n, m) => thisTest.run(n, m));
Une autre solution nécessitant une configuration initiale, mais payante avec sa légèreté invinciblement légère, littéralement en un mot, utilise Method Decorators pour lier les méthodes JIT à l'aide de getters.
J'ai créé un repo sur GitHub pour présenter une implémentation de cette idée (il est un peu long de s'adapter à une réponse avec ses 40 lignes de code, commentaires inclus ) , que vous utiliseriez aussi simplement:
class DemonstrateScopingProblems {
private status = "blah";
@bound public run() {
alert(this.status);
}
}
Je n'ai encore jamais vu cela mentionné, mais cela fonctionne parfaitement. En outre, cette approche ne présente pas d'inconvénient notable: la mise en œuvre de ce décorateur - , y compris une vérification de type pour la sécurité du type à l'exécution - est triviale. et simple, et vient avec essentiellement zéro overhead après l'appel de méthode initiale.
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.toString(), {
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];
}
L'idée peut également être poussée un peu plus loin, en effectuant cette opération dans un décorateur de classe, en effectuant une itération sur des méthodes et en définissant le descripteur de propriété ci-dessus pour chacune d'elles en une seule passe.
Nécromancie.
Il existe une solution simple, évidente, qui n'exige pas de fonctions fléchées (les fonctions fléchées sont 30% plus lentes), ni de méthodes JIT via des accesseurs.
Cette solution consiste à lier le this-context 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 dans le 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
Dans votre code, avez-vous essayé de changer la dernière ligne comme suit?
$(document).ready(() => thisTest.run());