Il faut vérifier le type d'instance de [this] dans le constructeur. Le problème est que, sans autre précision, cette approche est source d'erreurs. Il existe toutefois une solution.
Disons que nous avons affaire à la fonction ClassA(). L'approche rudimentaire est la suivante :
function ClassA() {
if (this instanceof arguments.callee) {
console.log("called as a constructor");
} else {
console.log("called as a function");
}
}
Il existe plusieurs raisons pour lesquelles la solution susmentionnée ne fonctionnera pas comme prévu. Considérez seulement les deux suivantes :
var instance = new ClassA;
instance.classAFunction = ClassA;
instance.classAFunction(); // <-- this will appear as constructor call
ClassA.apply(instance); //<-- this too
Pour y remédier, certains suggèrent soit a) de placer une information dans un champ de l'instance, comme "ConstructorFinished" et de la vérifier, soit b) de garder une trace de vos objets construits dans une liste. Je ne suis pas à l'aise avec ces deux solutions, car modifier chaque instance de ClassA est bien trop invasif et coûteux pour qu'une fonctionnalité liée au type fonctionne. Rassembler tous les objets dans une liste pourrait poser des problèmes de garbage collection et de ressources si ClassA a de nombreuses instances.
La solution consiste à pouvoir contrôler l'exécution de votre fonction ClassA. L'approche simple est la suivante :
function createConstructor(typeFunction) {
return typeFunction.bind({});
}
var ClassA = createConstructor(
function ClassA() {
if (this instanceof arguments.callee) {
console.log("called as a function");
return;
}
console.log("called as a constructor");
});
var instance = new ClassA();
Cela empêchera efficacement toute tentative de fraude avec la valeur [this]. Une fonction liée conservera toujours son contexte [this] original, sauf si vous l'appelez avec l'option nouveau opérateur.
La version avancée donne la possibilité d'appliquer le constructeur sur des objets arbitraires. On peut par exemple utiliser le constructeur comme convertisseur de type ou fournir une chaîne de constructeurs de classes de base appelables dans des scénarios d'héritage.
function createConstructor(typeFunction) {
var result = typeFunction.bind({});
result.apply = function (ths, args) {
try {
typeFunction.inApplyMode = true;
typeFunction.apply(ths, args);
} finally {
delete typeFunction.inApplyMode;
}
};
return result;
}
var ClassA = createConstructor(
function ClassA() {
if (this instanceof arguments.callee && !arguments.callee.inApplyMode) {
console.log("called as a constructor");
} else {
console.log("called as a function");
}
});