web-dev-qa-db-fra.com

Comment vérifier si une variable est une déclaration de classe ES6?

J'exporte la classe ES6 suivante à partir d'un module:

export class Thingy {
  hello() {
    console.log("A");
  }

  world() {
    console.log("B");
  }
}

Et l'importer depuis un autre module:

import {Thingy} from "thingy";

if (isClass(Thingy)) {
  // Do something...
}

Comment puis-je vérifier si une variable est une classe? Pas une classe instance, mais une classe déclaration?

En d'autres termes, comment pourrais-je implémenter la fonction isClass dans l'exemple ci-dessus?

23

Je vais être clair ici, toute fonction arbitraire peut être un constructeur. Si vous faites la distinction entre "classe" et "fonction", vous faites de mauvais choix de conception d'API. Si vous supposez que quelque chose doit être un class par exemple, personne utilisant Babel ou TypeScript ne sera détecté comme un class car leur code aura été converti en fonction à la place. Cela signifie que vous exigez que toute personne utilisant votre base de code doit s'exécute dans un environnement ES6 en général, donc votre code sera inutilisable sur les environnements plus anciens.

Vos options ici sont limitées au comportement défini par l'implémentation. Dans ES6, une fois le code analysé et la syntaxe traitée, il ne reste plus beaucoup de comportement spécifique à la classe. Tout ce que vous avez est une fonction constructeur. Votre meilleur choix est de faire

if (typeof Thingy === 'function'){
  // It's a function, so it definitely can't be an instance.
} else {
  // It could be anything other than a constructor
}

et si quelqu'un a besoin de faire une fonction non constructeur, exposez une API distincte pour cela.

Évidemment, ce n'est pas la réponse que vous cherchez, mais il est important de le préciser.

Comme l'autre réponse mentionne ici, vous avez une option parce que .toString() sur les fonctions est nécessaire pour retourner une déclaration de classe, par exemple.

class Foo {}
Foo.toString() === "class Foo {}" // true

L'essentiel, cependant, est que cela ne s'applique que si c'est possible. Il est conforme à 100% aux spécifications pour qu'une implémentation ait

class Foo{}
Foo.toString() === "throw SyntaxError();"

Non les navigateurs font actuellement cela, mais il existe plusieurs systèmes embarqués qui se concentrent sur la programmation JS par exemple, et pour préserver la mémoire de votre programme lui-même, ils ignorent le code source une fois qu'il a été analysé, ce qui signifie qu'ils le feront n'ont pas de code source à renvoyer de .toString() et cela est autorisé.

De même, en utilisant .toString(), vous faites des hypothèses sur la pérennité et la conception générale de l'API. Dis que tu le fais

const isClass = fn => /^\s*class/.test(fn.toString());

car cela repose sur des représentations de chaînes, il pourrait facilement se casser.

Prenons l'exemple des décorateurs:

@decorator class Foo {}
Foo.toString() == ???

La .toString() de ceci inclut-elle le décorateur? Que faire si le décorateur lui-même renvoie un function au lieu d'une classe?

14
loganfsmyth

Si vous voulez vous assurer que la valeur n'est pas seulement une fonction, mais vraiment une fonction constructeur pour une classe, vous pouvez convertir la fonction en chaîne et inspecter sa représentation. La spécification dicte la représentation sous forme de chaîne d'un constructeur de classe .

function isClass(v) {
  return typeof v === 'function' && /^\s*class\s+/.test(v.toString());
}

Une autre solution serait d'essayer d'appeler la valeur comme une fonction normale. Les constructeurs de classe ne peuvent pas être appelés en tant que fonctions normales, mais les messages d'erreur varient probablement selon les navigateurs:

function isClass(v) {
  if (typeof v !== 'function') {
    return false;
  }
  try {
    v();
    return false;
  } catch(error) {
    if (/^Class constructor/.test(error.message)) {
      return true;
    }
    return false;
  }
}

L'inconvénient est que l'invocation de la fonction peut avoir toutes sortes d'effets secondaires inconnus ...

27
Felix Kling

Peut-être que cela peut aider

let is_class = (obj) => {
    try {
        new obj();
        return true;
    } catch(e) {
        return false;
    };
};
0
aimadnet