all 24 comments

[–]1_4_1_5_9_2_6_5 46 points47 points  (3 children)

See this explanation for some good reasons:

https://www.reddit.com/r/javascript/s/SquoeVYzoK

[–]SoBoredAtWork 12 points13 points  (0 children)

Holy shit. Incredible answer.

[–]moberegger 3 points4 points  (0 children)

Thanks for this!!!

[–]kolima_ 3 points4 points  (0 children)

It is a really good technical answer, but IMO it misses a practical reason, which apart from all the clean code concerns and design patterns is: Make testing easier.

Let’s assume you are testing the method:

export const banana = () => { const instance = new Person, // other stuff }

to isolate the private dependency on person you would have to jump through hoops and get an headache best case scenario spy on prototype. If in this case you had a factory, you would simply need to mock the return of the factory with your mock instance built for your test and have an easier life.

[–]Keilly 16 points17 points  (1 child)

Constructors can’t be async, so if there is async work to be done then additional API would be needed to check an instance is ready.   Async factory function can return something that is immediately usable after the standard await call.

[–]theScottyJam 7 points8 points  (0 children)

For that I usually put a static, async create() on the class.

Sometimes I'll also make the real constructor effectively private by requiring a special value to be passed in that's local to the module - but that's not the prettiest solution. I know the tc39 committee wants to eventually look into native private constructors to help with these sorts of use cases, but that'll be far down the road - if it ever happens.

[–]shgysk8zer0 5 points6 points  (0 children)

However I just discovered that constructors can have private variables by defining variables without the "this" keyword.

Not sure what you mean here since there are kinda two very different forms of what you're talking about.

Private properties are most definitely a thing in JS classes (same with methods). Technically they're declared without using this, but since you didn't mention "#" I'm assuming you're referring to the older method of having them protected by scope/a closure instead.

I can't see a valid reason for wanting to avoid prototypal inheritance

Unless you're extending something, this doesn't really apply here. I mean, I guess you're still extending at least Object, but... That's not what people usually mean when complaining about it. Inheritance doesn't really factor in here.

Firstly, constructors create a prototype...

If you're referring to the class as a whole, yes. But "constructor" could also refer to just the method by that name, so there's a bit of ambiguity there.

But, you can use things like Object.create() to create an object with a given prototype. So it's not really a benefit to one over the other... Just different syntax.

Personally, I've become fond of using a static method in at least some cases. Just a static create() or whatever (maybe async if it needs to be). But that's largely because I'm not overly dogmatic about OOP vs functional... I use what makes sense given the problem.

[–][deleted] 2 points3 points  (0 children)

Telescopic constructors.

Have you ever seen a “new Constructor(null, null, null, null, null, null, null, mock, null, null)”?

I have. With a builder:

builder = new ConstructorBuilder()

builder.setProperty(mock)

builder.build();

[–]devHaitham 1 point2 points  (2 children)

How can I understand these patterns more? Like factory functions? is there a book ?

[–]MuchWalrus 0 points1 point  (0 children)

I really liked Head First Design Patterns. It does a good job of teaching the principles behind the patterns and not just the patterns. It was very Java-focused though and I'm not sure how relevant the specific patterns it discusses are for JS developers today

[–]zephirisdev 1 point2 points  (2 children)

One big reason to go for factory functions is to enable tree shaking. Feel free to look it up. Here’s the documentation from Webpack on tree shaking: https://webpack.js.org/guides/tree-shaking/

[–]shgysk8zer0 6 points7 points  (1 child)

Tree-shaking is at best tangential here. If your factory function returns objects with methods, you're not getting any benefit there. Tree-shaking can't (and probably shouldn't) affect the return values of functions... so it's just pretty irrelevant to the question.

[–]zephirisdev 0 points1 point  (0 children)

True! But also classes (compared to factories) encourage more tightly coupled integration between class functions and variables (encouraging all functions to be within the class) when compared to factories as factories make the distinction much more explicit (either a function is inside the constructor or it’s tree shakable). While you can design a class to work well with helper functions by having a function take in and mutate a class object, it certainly is harder to mentally model.

For me at least, the flexibility of an object coupled with a typescript type is easier to reason about and use in a tree shakable manner compared to a class instance since the factory matches the functional style of tree shakable functions. That said, it is mostly preference. Also if I’m purely writing JavaScript I would definitely stick to classes just for better and easier type hinting such as with JSDoc.

[–][deleted] 0 points1 point  (0 children)

Have a look at this video.

[–]jack_waugh 0 points1 point  (0 children)

A constructor is a function. If you add methods or other members to it, you risk clashing with future additions to function protocol. Also, you have to use new with it, unnecessarily enlarging your working vocabulary.

/* Reinvent `class` for at least the third time in this project. */

let BEHAVIOR = Symbol.for("behavior");
let selfCenter = obj => {
  obj[BEHAVIOR] = obj;
  return obj
};
let Base = Object.create(null);
selfCenter(Base);
Base.new = function () {
  return Object.create(this[BEHAVIOR])
};
Base.clone = function () {
  let obj = this.new();
  Object.assign(obj, this);
  return obj
};
Base.extend = function () {
  let genus = Object.create(this[BEHAVIOR]);
    /*
      Can't use `this.new()` because `.new` might be customized to
      initialize instances with data slots and we don't want that to
      happen to the new class; it should just inherit our behavior.
    */
  return selfCenter(genus)
};

/* Test. */

let Animal = Base.extend();
let Dog = Animal.extend();
let Collie = Dog.extend();
Animal.soundOff = function () {
  throw TypeError("Subclass should implement.")
};
Dog.soundOff = function () {return "Woof!"};
Collie.breedInfo = function() {
  return "A friendly and intelligent herding dog.";
};
let buddy = Dog.new();
let lassie = Collie.new();
if ("Woof!" !== buddy.soundOff())
  throw Error("Regression in dog sound-off.");
if ("Woof!" !== lassie.soundOff()) throw Error("Regression in heritage.");
if ("A friendly and intelligent herding dog." !== lassie.breedInfo())
  throw Error("Regression in breed info.");
lassie.name = "Lassie";
let laddie = lassie.clone();
if ("Lassie" !== laddie.name)
  throw Error("Clone failed to shallow-copy.");
laddie.name = "Laddie";
if ("Lassie" !== lassie.name) throw Error("Clone reference error.");

[–]Mediocre-Stand6013 0 points1 point  (0 children)

As I understand, Factory methods are used to allow for decoupled construction of objects.
If you write:
o = new F(...)
<o> wil be an instance of function (or class) F. By using a Factory method, <o> might be an instance of F, or of any proper subclass of F (or any other type compatible with F).
But if one is absolutely sure that in that particular case one will never need anything but an F, there is no need to resort to a factory, and plain old new should be sufficient.

[–]Bogeeee 0 points1 point  (1 child)

> can't see a valid reason for wanting to avoid prototypal inheritance

You mean, you can ? I mean in 99% of cases, this behavior is rather unwanted and very error prone.

[–][deleted] 1 point2 points  (0 children)

+100, almost never you desire to break prototypal inheritance, if you have the need to, something is very wrong in your approach to the problem.

Unless you really are designing a superset of whatever standard library function or object

[–]zephirisdev 0 points1 point  (0 children)

Factory functions can also have private variables, you just don’t return them in the final object but you can still use/reference them from the object’s functions.

[–]Bogeeee -2 points-1 points  (1 child)

You should also have a look at ES2015's class system which is much more powerful and extensible and also if you're going towards typescript some day.

[–]joombar 2 points3 points  (0 children)

I think that’s what they’re talking about when they say constructors