8

I'm reading this article about perils of trying to mimic OOP in JavaScript and there's the following:

In JavaScript, factory functions are simply constructor functions minus the new requirement, global pollution danger and awkward limitations (including that annoying initial capitalized letter convention).

JavaScript doesn’t need constructor functions because any function can return a new object. With dynamic object extension, object literals and Object.create(), we have everything we need — with none of the mess. And this behaves just like it does in any other function. Hurray!

Am I right to assume that given this approach we should replace this code:

function Rabbit() {
    this.speed = 3;
}

Rabbit.prototype = {
    this.getSpeed = function() {
        return this.speed;
    }
}

var rabbit = new Rabbit();

With this:

function RabbitFactory() {
    var rabbit = {
        speed: 3
    };

    Object.setPrototypeOf(rabbit, {
        getSpeed: function() {
            return this.speed;
        }
    })

    return rabbit;
}

var rabbit = RabbitFactory();
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • 1
    I think you are over-engineering this a bit. You've fallen to another trap of mimicing OOP. A _factory function_ is just a function, what makes it a _factory_ is that you can use `new` with it to get an instance. That's your first example. The second one is doing a similar thing to what would happen if you call `bunny = new Rabbit()` as you are replacing this with `bunny = RabbitFactory()` yet the latter is less flexible. What the article suggests is to use _less_ code, so what you should do is something like `bunny = Object.create(rabbit)` – VLAZ Sep 17 '16 at 12:34
  • Example of factory functions () => ({}) // returns object () => [] // returns array – Ali Rehman Aug 23 '22 at 07:08

6 Answers6

10

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.

Artyom Pranovich
  • 6,814
  • 8
  • 41
  • 60
7

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 Object constructor
  • 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 the GetSpeed getter)
Tzahi Leh
  • 2,002
  • 1
  • 15
  • 27
  • Could you also add an example of a Factory function? – Max Vollmer Oct 18 '19 at 19:47
  • Ah, sorry! I guess I misunderstood "Constructor functions look like the following:" I am sorry, I was just reviewing the "late answer" review queue, I am not an expert on JS at all. Maybe you can reword your answer a bit, to avoid other people misunderstanding it as I did. Thanks! – Max Vollmer Oct 18 '19 at 21:16
  • @MaxVollmer, I am sorry because that was a mistake of mine.. meant factory function ><.. I fixed it. For further explanation of differences and pros and cons, you can read [this](https://stackoverflow.com/questions/8698726/constructor-function-vs-factory-functions#targetText=A%20constructor%20returns%20an%20instance,has%20a%20large%20setup%20process.) stackoverflow thread – Tzahi Leh Oct 19 '19 at 10:15
4

No, that is wrong. You should not use Object.setPrototypeOf, better use Object.create (though it makes no difference for the result). And if you create the prototype from an object literal every time, it loses all of its sharing advantages, so you should either drop that completely or move it outside the function to make it static. The correct™ way to write the factory function would be

const protoRabbit = {
    getSpeed: function() {
        return this.speed;
    }
};
function createRabbit() {
    var rabbit = Object.create(protoRabbit);
    rabbit.speed = 3;
    return rabbit;
}

var rabbit = createRabbit();
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • thanks, do you write your code in that style without constructor functions? – Max Koretskyi Sep 17 '16 at 13:11
  • @Maximus Occasionally, when it fits :-) Usually I go for ES6 `class`es though. I've rarely found "awkward limitations" in constructors. – Bergi Sep 17 '16 at 13:13
  • I see, thanks. I'm just getting into all this `functional` buzz around `JavaScript` and not sure whether I should focus on it. What do you think? – Max Koretskyi Sep 17 '16 at 13:20
  • Functional programming is very powerful and I can only recommend to learn about it, but maybe Haskell is a better language to learn it. JavaScript is a bit limited in some regards, so you should first learn what is possible in general and then be able to decide how far you can sensibly apply it in js. – Bergi Sep 17 '16 at 14:05
  • Yeah, maybe I'll get to Haskell some day. Thanks – Max Koretskyi Sep 17 '16 at 14:08
3

I don't like these answers because they use the this keyword. You can avoid this altogether and still use a factory function like this:

function createRabbit() {
    var speed = 3;
    return {
        getSpeed: function() {
            return speed;
        }
    }
}

var rabbit = createRabbit();
console.log(rabbit.getSpeed());

This guy has a good article about it: http://radar.oreilly.com/2014/03/javascript-without-the-this.html

Eric Moore
  • 101
  • 7
2

The simplest pattern is:

function RabbitFactory() {
  return {
    speed: 3,
    getSpeed() { return this.speed; }
  };
}

var rabbit = RabbitFactory();
0

This text awfully sounds like a Douglas Crockford speech. Anyway it mentions this pattern.

function RabbitFactory(){
rabbit = Object.create({ getSpeed: function() {
                                     return this.speed;
                                   }
                       });
rabbit.speed = 3;
return rabbit;
}

setPrototypeOf/getPrototypeOf or the __proto__ property are introduced in ES6 whereas Object.create() is an ES5 functionality. setPrototypeOf/getPrototypeOf or the __proto__ are good for subclassing thingies but you shouldn't use it together with Object.create()

I would keep using constructor functions though.

Redu
  • 25,060
  • 6
  • 56
  • 76
  • 3
    Don't create prototypes inside a function. – Bergi Sep 17 '16 at 12:48
  • 1
    Don't even mention `__proto__`, it's deprecated and should be removed from our minds. Btw, `getPrototypeOf` is ES5 – Bergi Sep 17 '16 at 12:49
  • @Bergi The pattern you give is an exact copy of my snippet other than i am using an object literal whereas you are using a reference for the prototype. On the other hand `__proto__` is not depreciated please don't mislead people. It's standardized in ES6 and a beautiful tool use when needed. – Redu Sep 17 '16 at 12:53
  • 2
    The location of the object literal matters, that's what I tried to point out :-) – Bergi Sep 17 '16 at 12:55
  • `__proto__` *is* deprecated, if you don't believe it read the spec yourself. It's been standardised for backwards-compatibility only, and should not be used anywhere any more. `getPrototypeOf`/`setPrototypeOf` are the proper tools that ES6 gives us. – Bergi Sep 17 '16 at 12:57
  • @Bergi [__proto__ has a standard in ES6 and a draft in ES7](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto#Specifications) and will indefinitely remain as a part of the language. `getPrototypeOf` / `setPrototypeOf` does exactly the same thing through a function so one can chose whatever suits best. And... as far as i know the location of the object literal does not matter at all. I would love to learn more about it if you could point me to a resource. – Redu Sep 17 '16 at 13:06
  • Have a look at [this answer](http://stackoverflow.com/a/36061819/1048572) where I cite the relevant parts of the spec – Bergi Sep 17 '16 at 13:11
  • @Redu a new object literal is created every time you call `RabbitFactory()` creating additional memory allocation as `Object.create()` won't be pointing to the same object literal when `RabbitFactory()` is called. Create two objects with your code and two objects with Bergi's and compare the `getSpeed()` methods from each set via `===`. Bergi's will equal `true` since they're both sharing the same prototype and yours will be `false` because they do not, [JSFiddle](https://jsfiddle.net/92n6rLjs/1/). – hungerstar Jul 11 '17 at 15:45