Following is an article from a couple years ago that keeps it simple, but does a good job of covering all the bases. Follow up study and experimenting is up to the reader.
In Depth - JavaScript Factory Functions
This link was posted here a couple of years ago in the public interest but the article is still there and just as valid and meaningful today as it was then.
Hope it opens some doors in your mind. Don’t be afraid to bring up any other question around this topic. We do still need to extrapolate the differences between in the original question.
Continuing…
Let’s look at something simple and compare…
foo = (name, city) => {
return {
name,
city
}
};
wee = foo('Wee Gillis', 'St. William')
wee.name // Wee Gillis
wee.city // St. William
wee instanceof foo
Uncaught TypeError: Function has non-object prototype ‘undefined’ in instanceof check
wee instanceof Object
true
Now let’s look a class object instance…
class Foo {
constructor (name, city) {
name,
city
}
}
> gillis = new Foo('Wee Gillis', 'St. William')
<- Foo {}
As I interpret this, it tells us that the constructor does not support destructuring. Mind, this is not an expert view, only what we observe.
class Foo {
constructor (name, city) {
this.name = name;
this.city = city;
}
}
> gillis = new Foo('Wee Gillis', 'St. William')
<- Foo {name: "Wee Gillis", city: "St. William"}
> gillis instanceof Foo
<- true
Clearly, if we wish to identify objects as being related to one another by their similarity and purpose object literals do not offer the same distinction as class instances. If we wish to give our object literals more functionality we have to modify the Object.prototype
which is never a good idea.
If we wish to add functionality to a class, and have it reach across all instances, instantly, then we have the constructor prototype to work with and it has no bearing on the Object prototype. We’re in safe territory.
This is not necessarily an advantage in a plain sense. Our overall design and motivation is at the core of what functionality, especially dynamic functionality we will need in any one session. See it only as a difference.
> Foo.prototype.getName = function() { return this.name }
<- ƒ () { return this.name }
> gillis.getName()
<- "Wee Gillis"
> Foo.prototype.getCity = function() { return this.city }
<- ƒ () { return this.city }
> gillis.getCity()
<- "St. William"
A factory object does not have this, on the fly functionality, but what of it? If we don’t need it, then we are leaning the right way in terms of simplicity.
We may conclude that a factory object is not a class and is not a constructor. It is a plain object with only Object.prototype to inherit from. We do not invoke an instance with new
, but only assignment. They are very session oriented which likely makes them easy to work with.
Remember, factory objects are similar to each other if coming from the same factory function, but they have no way of knowing their similarity since they only have Object.prototype in their inheritence chain. Everything is an object so that makes them pretty generic. Our program logic needs to draw the correlation between these objects.
Here is where a data structure comes in to help organize loosely created object instances returned from the same factory. Put them all in the same array.
> foos = []
<- []
> foos.push(foo('Wee Gillis', 'St. William'))
<- 1
> foos.push(foo('MTF', 'Edmonton'))
<- 2
> foos
<- (2) [{…}, {…}]
0: {name: "Wee Gillis", city: "St. William"}
1: {name: "MTF", city: "Edmonton"}
length: 2
__proto__: Array(0)
> foos.map((x) => Object.values(x))
<- (2) [Array(2), Array(2)]
0: (2) ["Wee Gillis", "St. William"]
1: (2) ["MTF", "Edmonton"]
length: 2
__proto__: Array(0)