Frank
Frank

Reputation: 2230

Is there a downside to using closures over prototypes?

I like the general style of using closures to create objects with private properties. What I'm unsure of is whether it's more efficient to create the prototype methods within a closure or on the actual prototype of the object. Consider the following examples:

const A = function(a, b) {

  this.a = a;
  this.b = b;

};

A.prototype = {

  add:function() { this.a += this.b; }

};

const B = (function() {

  function add() { this.a += this.b; }

  return function(a, b) {

    return { a, b, add };

  };

})();

Example A is a traditional constructor function with a prototype. Using it looks like this:

var a = new A(1, 1);
a.add(); // a.a == 2;

Example B is the technique using closures. Using it looks like this:

var b = B(1, 1);
b.add(); // b.a == 2;

The way I expect a to work is that each instance of A has a and b properties as well as a pointer to the prototype object which holds the add method.

The way I expect b to work is that each instance of B has a and b properties as well as a pointer to the add method. The B function has a closure that contains the add method. The add method is defined only once when B is defined. This seems like a decent way of creating "prototype" properties and methods in JS. Does anyone know if this is a performant approach to creating objects with "shared" properties or a viable alternative to the traditional approach using prototype?

Upvotes: 2

Views: 227

Answers (2)

Aadit M Shah
Aadit M Shah

Reputation: 74214

Even better, don't use prototypes or closures. Just use regular functions.

const C = (a, b) => ({ a, b });

function add(c) {
    c.a += c.b;
}

const c = C(1, 1);
add(c); // c.a === 2;

console.log(c); // { a: 2, b: 1 }

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1074585

Your closure example is slightly atypical for that style of builder function because it doesn't create any functions in B itself, just in the anonymous function that created B; that means it avoids the usual minor downside of doing that (recreating functions each time B is called). (Usually the "closure style" creates functions in B itself so it can use a and b in the execution context directly, rather than using this.a and this.b properties.)

I can't see any performance downside to what you're doing. In theory there'd be a very slight performance upside to it (completely apart from your observation that the object literal is much faster than using new) because the methods on the instance are own properties rather than inherited ones and so (in theory) they'd be slightly faster to look up (because the JavaScript engine finds them right away on the object, rather than not finding them on the object itself and having to go look at the prototype). In reality, though, I would expect any modern JavaScript engine to optimize such that you'd get no benefit to that.

I can see a couple of non-performance downsides, but nothing major:

  1. Everything is just an Object, so if you're debugging an unrelated performance problem and looking at a heap snapshot, everything is just Object rather than being A or B or C, which makes using the memory profiler harder. I tried to get the memory profile to categorize them by adding a constructor property to them, but it didn't work (in Chrome, anyway). I think it uses the object's prototype's constructor function. You could work around it by giving the objects a constructor-specific prototype, just not using it, but that seems kind of strange.

  2. To enhance an object without using a prototype object, you have to use mixins instead, which means copying all of the properties from one object to another, which is more work and means the objects are each larger than they would be if they used prototypical inheritance, potentially creating memory churn on lower-end devices (mobiles, etc.). (Alternatively, use composition rather than enhancing, which there are separate arguments for doing.)

  3. You're swimming against the language. JavaScript's prototypical nature is a big part of what it is (even if you don't use constructor functions and instead use Object.create or similar). JavaScript engines are designed to be good at optimizing prototypical relationships. You'd be doing something else instead. It may well be just fine, it's just something that seems less than ideal.

  4. It's harder for other people to get up to speed on a codebase when the pattern is uses is atypical. The three typical ways of using JavaScript are constructor functions (with prototypes), the non-this closure/Object.create way (with prototypes), and functional programming. What you're looking at is sort of a mix of the first two.

But if you're solving a specific problem related to the speed of object creation, targeted use of this slightly unusual pattern may be very successful. Or you may just prefer it and be happy to take the downsides with the upside. :-)

Upvotes: 2

Related Questions