Réponses:
La différence fondamentale est qu'une fonction constructeur est utilisée avec le new
mot - clé (ce qui oblige JavaScript à créer automatiquement un nouvel objet, à définir this
dans la fonction cet objet et à renvoyer l'objet):
var objFromConstructor = new ConstructorFunction();
Une fonction d'usine est appelée comme une fonction "régulière":
var objFromFactory = factoryFunction();
Mais pour qu'elle soit considérée comme une "fabrique", elle aurait besoin de retourner une nouvelle instance d'un objet: vous ne l'appelleriez pas une fonction "fabrique" si elle retournait simplement un booléen ou quelque chose. Cela ne se produit pas automatiquement comme avec new
, mais cela permet une plus grande flexibilité dans certains cas.
Dans un exemple très simple, les fonctions référencées ci-dessus pourraient ressembler à ceci:
function ConstructorFunction() {
this.someProp1 = "1";
this.someProp2 = "2";
}
ConstructorFunction.prototype.someMethod = function() { /* whatever */ };
function factoryFunction() {
var obj = {
someProp1 : "1",
someProp2 : "2",
someMethod: function() { /* whatever */ }
};
// other code to manipulate obj in some way here
return obj;
}
Bien sûr, vous pouvez rendre les fonctions d'usine beaucoup plus compliquées que ce simple exemple.
L'un des avantages des fonctions d'usine est que l'objet à renvoyer peut être de plusieurs types différents en fonction de certains paramètres.
someMethod
les objets retournés par l'usine, et c'est là que cela devient un peu brumeux. À l'intérieur de la fonction d'usine, si on le fait simplement var obj = { ... , someMethod: function() {}, ... }
, cela conduirait à ce que chaque objet retourné contienne une copie différente someMethod
dont nous pourrions ne pas vouloir. C'est là que l'utilisation new
et prototype
à l'intérieur de la fonction d'usine aideraient.
new
avec la fonction constructeur; Je pensais que c'était là où il faudrait peut-être voir un exemple de remplacement des constructeurs par des fonctions d'usine et c'est là que je pensais que la cohérence des exemples était requise. Quoi qu'il en soit, la réponse est suffisamment informative. C'était juste un point que je voulais soulever, non pas que je mette en cause la qualité de la réponse de quelque manière que ce soit.
new
interne, ou utiliser Object.create()
pour créer un objet avec un prototype spécifique.
La plupart des livres vous apprennent à utiliser les constructeurs et new
this
fait référence au nouvel objet
Certaines personnes aiment la façon dont var myFoo = new Foo();
lit.
Les détails de l'instanciation sont divulgués dans l'API appelante (via l' new
exigence), de sorte que tous les appelants sont étroitement couplés à l'implémentation du constructeur. Si jamais vous avez besoin de la flexibilité supplémentaire de l'usine, vous devrez refactoriser tous les appelants (certes le cas exceptionnel, plutôt que la règle).
L'oubli new
est un bogue si courant, vous devriez fortement envisager d'ajouter une vérification standard pour vous assurer que le constructeur est appelé correctement ( if (!(this instanceof Foo)) { return new Foo() }
). EDIT: Depuis ES6 (ES2015), vous ne pouvez pas oublier new
avec un class
constructeur, sinon le constructeur lancera une erreur.
Si vous effectuez la instanceof
vérification, cela laisse une ambiguïté quant à savoir si new
c'est nécessaire ou non . À mon avis, cela ne devrait pas être le cas. Vous avez effectivement court-circuité l' new
exigence, ce qui signifie que vous pouvez supprimer l'inconvénient n ° 1. Mais alors, vous avez juste une fonction d'usine dans tout sauf le nom , avec un passe-partout supplémentaire, une lettre majuscule et un this
contexte moins flexible .
Mais ma principale préoccupation est qu'elle viole le principe ouvert / fermé. Vous commencez par exporter un constructeur, les utilisateurs commencent à utiliser le constructeur, puis vous réalisez que vous avez besoin de la flexibilité d'une fabrique, à la place (par exemple, pour basculer l'implémentation pour utiliser des pools d'objets, ou pour instancier entre les contextes d'exécution, ou pour ont plus de flexibilité d'héritage en utilisant le prototypage OO).
Vous êtes coincé, cependant. Vous ne pouvez pas effectuer la modification sans casser tout le code qui appelle votre constructeur avec new
. Vous ne pouvez pas passer à l'utilisation de pools d'objets pour des gains de performances, par exemple.
De plus, l'utilisation de constructeurs vous donne un caractère trompeur instanceof
qui ne fonctionne pas dans les contextes d'exécution et ne fonctionne pas si votre prototype de constructeur est remplacé. Cela échouera également si vous commencez à revenir this
de votre constructeur, puis que vous passez à l'exportation d'un objet arbitraire, ce que vous devrez faire pour activer le comportement de type usine dans votre constructeur.
Moins de code - aucun passe-partout requis.
Vous pouvez renvoyer n'importe quel objet arbitraire et utiliser n'importe quel prototype arbitraire, ce qui vous donne plus de flexibilité pour créer différents types d'objets qui implémentent la même API. Par exemple, un lecteur multimédia qui peut créer des instances de lecteurs HTML5 et Flash, ou une bibliothèque d'événements qui peut émettre des événements DOM ou des événements de socket Web. Les usines peuvent également instancier des objets dans des contextes d'exécution, tirer parti des pools d'objets et permettre des modèles d'héritage prototypique plus flexibles.
Vous n'auriez jamais besoin de passer d'une usine à un constructeur, donc la refactorisation ne sera jamais un problème.
Aucune ambiguïté sur l'utilisation new
. Ne fais pas ça. (Cela va faire this
mal se comporter, voir point suivant).
this
se comporte normalement - vous pouvez donc l'utiliser pour accéder à l'objet parent (par exemple, à l'intérieur player.create()
, this
fait référence à player
, comme n'importe quel autre appel de méthode le ferait. call
et apply
également réassigner this
, comme prévu. Si vous stockez des prototypes sur l'objet parent, cela peut être un excellent moyen de permuter dynamiquement les fonctionnalités et d'activer un polymorphisme très flexible pour votre instanciation d'objet.
Aucune ambiguïté sur l'opportunité de capitaliser ou non. Ne fais pas ça. Les outils Lint se plaindront, puis vous serez tenté d'essayer de les utiliser new
, puis vous annulerez l'avantage décrit ci-dessus.
Certaines personnes aiment la manière var myFoo = foo();
ou var myFoo = foo.create();
lit.
new
ne se comporte pas comme prévu (voir ci-dessus). Solution: ne l'utilisez pas.
this
ne fait pas référence au nouvel objet (à la place, si le constructeur est invoqué avec une notation par points ou une notation entre crochets, par exemple foo.bar () - this
fait référence à foo
- comme toute autre méthode JavaScript - voir les avantages).
new
viole le principe ouvert / fermé. Voir medium.com/javascript-scene/ ... pour une discussion beaucoup plus large que ne le permettent ces commentaires.
new
mot - clé, je ne pense pas que le new
mot - clé offre réellement une lisibilité supplémentaire. OMI, il semble idiot de sauter à travers des cerceaux afin de permettre aux appelants de taper plus.
Un constructeur renvoie une instance de la classe sur laquelle vous l'appelez. Une fonction d'usine peut tout renvoyer. Vous utiliseriez une fonction d'usine lorsque vous avez besoin de renvoyer des valeurs arbitraires ou lorsqu'une classe a un processus de configuration important.
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
new
crée un objet prototypé sur User.prototype
et appelle User
avec l'objet créé comme this
valeur.
new
traite une expression d'argument pour son opérande comme facultative:
let user = new User;
provoquerait un new
appel User
sans arguments.
new
renvoie l'objet qu'il a créé, sauf si le constructeur renvoie une valeur d'objet , qui est renvoyée à la place. Il s'agit d'un cas de bord qui, pour la plupart, peut être ignoré.
Les objets créés par des fonctions de constructeur héritent des propriétés de la propriété du constructeur prototype
et retournent true à l'aide de l' instanceOf
opérateur de la fonction de constructeur.
Les comportements ci-dessus peuvent échouer si vous modifiez dynamiquement la valeur de la prototype
propriété du constructeur après avoir déjà utilisé le constructeur. Cela est rare et ne peut pas être modifié si le constructeur a été créé à l'aide du class
mot - clé.
Les fonctions du constructeur peuvent être étendues à l'aide du extends
mot - clé.
Les fonctions constructeur ne peuvent pas être renvoyées en null
tant que valeur d'erreur. Puisqu'il ne s'agit pas d'un type de données objet, il est ignoré par new
.
function User(name, age) {
return {
name,
age,
}
};
let user = User("Tom", 23);
Ici, la fonction d'usine est appelée sans new
. La fonction est entièrement responsable de l'utilisation directe ou indirecte de ses arguments et du type d'objet qu'elle renvoie. Dans cet exemple, il renvoie un simple [Object object] avec certaines propriétés définies à partir des arguments.
Cache facilement les complexités d'implémentation de la création d'objet à l'appelant. Ceci est particulièrement utile pour les fonctions de code natif dans un navigateur.
La fonction d'usine n'a pas toujours besoin de renvoyer des objets du même type, et pourrait même revenir en null
tant qu'indicateur d'erreur.
Dans des cas simples, les fonctions d'usine peuvent être simples dans leur structure et leur signification.
Les objets retournés n'héritent généralement pas de la prototype
propriété de la fonction de fabrique et retournent à false
partir de instanceOf factoryFunction
.
La fonction de fabrique ne peut pas être étendue en toute sécurité à l'aide du extends
mot - clé car les objets étendus hériteraient de la prototype
propriété des fonctions de fabrique au lieu de la prototype
propriété du constructeur utilisée par la fonction de fabrique.
Les usines sont «toujours» meilleures. Lorsque vous utilisez des langages orientés objet,
Les implémentations (les objets réels créés avec new) ne sont pas exposées à l'utilisateur / consommateur d'usine. Cela signifie que le développeur d'usine peut s'étendre et créer de nouvelles implémentations tant qu'il / elle ne rompt pas le contrat ... et cela permet au consommateur d'usine de simplement bénéficier de la nouvelle API sans avoir à changer son code ... s'ils ont utilisé une nouvelle implémentation et qu'une "nouvelle" implémentation arrive, alors ils doivent changer chaque ligne qui utilise "new" pour utiliser la "nouvelle" implémentation ... avec l'usine leur code ne change pas ...
Usines - mieux que tout autre chose - le cadre de printemps est entièrement construit autour de cette idée.
Les usines sont une couche d'abstraction et, comme toutes les abstractions, elles ont un coût en complexité. Lorsque vous rencontrez une API basée en usine, déterminer quelle est l'usine pour une API donnée peut être difficile pour le consommateur d'API. Avec les constructeurs, la découvrabilité est triviale.
Lorsque vous décidez entre les cteurs et les usines, vous devez décider si la complexité est justifiée par l'avantage.
Il convient de noter que les constructeurs Javascript peuvent être des usines arbitraires en renvoyant autre chose que ceci ou indéfini. Ainsi, dans js, vous pouvez obtenir le meilleur des deux mondes: API détectable et mise en pool / mise en cache d'objets.
new
, en modifiant le comportement de this
, en modifiant la valeur de retour, en connectant une référence de prototype, en activant instanceof
(qui ment et ne doit pas être utilisé à cette fin). Apparemment, tous ces éléments sont des "caractéristiques". En pratique, ils nuisent à la qualité de votre code.
Pour les différences, Eric Elliott a très bien précisé,
Mais pour la deuxième question:
Quand utiliser l'un au lieu de l'autre?
Si vous venez de l'arrière-plan orienté objet, la fonction Constructor vous semble plus naturelle. de cette façon, vous ne devez pas oublier d'utiliser le new
mot-clé.