Videos
Basically I would distinguish 3 approaches to create an object in JS:
- Class
- Constructor
- Factory
Here are 3 examples (considering your Rabbit's one)
// class
class Rabbit {
constructor() {
this.speed = 3;
// it would be so nice to have just 'static const speed = 3;' instead of
// using constructor for that
}
getSpeed() {
return this.speed;
}
}
let rabbit1 = new Rabbit();
// constructor
function ConstructorRabbit(){ }
ConstructorRabbit.prototype.speed = 3;
ConstructorRabbit.prototype.getSpeed = function() {
return this.speed;
};
let rabbit2 = new ConstructorRabbit();
// factory
const rabbitProto = {
speed: 3,
getSpeed() {
return this.speed;
}
};
function factoryRabbit () {
return Object.create(rabbitProto);
}
let rabbit3 = factoryRabbit();
I'm not sure that there are so many pros to use only factory for creating objects, but probably I can single out the one. As mentioned in the article if we refer to very famous 'Design Patterns', so we should prefer object composition instead of class inheritance. And I'm totally agree with that postulate, thus returning back to JS and ES6 classes, we can say that prototype delegation may be better than class inheritance in some cases.
But also, we shouldn't forget this (as mentioned in the article as well) statement: "How itโs implemented doesnโt matter at all unless itโs implemented poorly". And this one, I would say, is a really good one.
Many answers here suggest Constructor Functions, although the name of the questions has to do with Factory Functions.
Factory Functions look like the following:
const RabbitFactory = () => {
const speed = 3;
const getSpeed = () => speed;
return { getSpeed }
}
const rabbit = RabbitFactory();
rabbit.getSpeed() // -> 3
rabbit.speed // -> undefined!
I like Factory functions more than Constructor function because:
- You don't need to get messy with prototyping
- You don't need to use the
Objectconstructor - Gives you the ability to choose what is private and what is public in your factory
(in my example, speed is private and this is a good practice. If you want to read the value of rabbit's speed, use theGetSpeedgetter)
The basic difference is that a constructor function is used with the new keyword (which causes JavaScript to automatically create a new object, set this within the function to that object, and return the object):
var objFromConstructor = new ConstructorFunction();
A factory function is called like a "regular" function:
var objFromFactory = factoryFunction();
But for it to be considered a "factory" it would need to return a new instance of some object: you wouldn't call it a "factory" function if it just returned a boolean or something. This does not happen automatically like with new, but it does allow more flexibility for some cases.
In a really simple example the functions referenced above might look something like this:
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;
}
Of course you can make factory functions much more complicated than that simple example.
One advantage to factory functions is when the object to be returned could be of several different types depending on some parameter.
Benefits of using constructors
Most books teach you to use constructors and
newthisrefers to the new objectSome people like the way
var myFoo = new Foo();reads.
Drawbacks
Details of instantiation get leaked into the calling API (via the
newrequirement), so all callers are tightly coupled to the constructor implementation. If you ever need the additional flexibility of the factory, you'll have to refactor all callers (admittedly the exceptional case, rather than the rule).Forgetting
newis such a common bug, you should strongly consider adding a boilerplate check to ensure that the constructor is called correctly (if (!(this instanceof Foo)) { return new Foo() }). EDIT: Since ES6 (ES2015) you can't forgetnewwith aclassconstructor, or the constructor will throw an error.If you do the
instanceofcheck, it leaves ambiguity as to whether or notnewis required. In my opinion, it shouldn't be. You've effectively short circuited thenewrequirement, which means you could erase drawback #1. But then you've just got a factory function in all but name, with additional boilerplate, a capital letter, and less flexiblethiscontext.
Constructors break the Open / Closed Principle
But my main concern is that it violates the open/closed principle. You start out exporting a constructor, users start using the constructor, then down the road you realize you need the flexibility of a factory, instead (for instance, to switch the implementation to use object pools, or to instantiate across execution contexts, or to have more inheritance flexibility using prototypal OO).
You're stuck, though. You can't make the change without breaking all the code that calls your constructor with new. You can't switch to using object pools for performance gains, for instance.
Also, using constructors gives you a deceptive instanceof that doesn't work across execution contexts, and doesn't work if your constructor prototype gets swapped out. It will also fail if you start out returning this from your constructor, and then switch to exporting an arbitrary object, which you'd have to do to enable factory-like behavior in your constructor.
Benefits of using factories
Less code - no boilerplate required.
You can return any arbitrary object, and use any arbitrary prototype - giving you more flexibility to create various types of objects which implement the same API. For example, a media player that can create instances of both HTML5 and flash players, or an event library which can emit DOM events or web socket events. Factories can also instantiate objects across execution contexts, take advantage of object pools, and allow for more flexible prototypal inheritance models.
You'd never have a need to convert from a factory to a constructor, so refactoring will never be an issue.
No ambiguity about using
new. Don't. (It will makethisbehave badly, see next point).thisbehaves as it normally would - so you can use it to access the parent object (for example, insideplayer.create(),thisrefers toplayer, just like any other method invocation would.callandapplyalso reassignthis, as expected. If you store prototypes on the parent object, that can be a great way to dynamically swap out functionality, and enable very flexible polymorphism for your object instantiation.No ambiguity about whether or not to capitalize. Don't. Lint tools will complain, and then you'll be tempted to try to use
new, and then you'll undo the benefit described above.Some people like the way
var myFoo = foo();orvar myFoo = foo.create();reads.
Drawbacks
newdoesn't behave as expected (see above). Solution: don't use it.thisdoesn't refer to the new object (instead, if the constructor is invoked with dot notation or square bracket notation, e.g. foo.bar() -thisrefers tofoo- just like every other JavaScript method -- see benefits).