Jesse
Jesse

Reputation: 6995

Class vs function returning object

I've noticed there are two different ways to effectively write a class (or class-like thing) in JavaScript. Using a simple counter class as an example:

A real class (used as new Counter1())

class Counter1 {
  constructor(count = 0) {
    this.count = count;
  }
  inc() {
    this.count++;
  }
  get() {
    return this.count;
  }
}

A function returning an object (used as Counter2())

function Counter2(count = 0) {
  return {
    inc() {
      count++;
    },
    get() {
      return count;
    },
  };
}

I have a lot of code that could be written in either one of these styles. Which should I use?

This is my understanding:

Counter1 can be used with instanceof and inheritance, but is more verbose and doesn't have real private properties (eg count property is exposed).

Counter2 is more concise and has real private methods/state (just local variables in the function), but can't be used with instanceof and inheritance. It also creates new copies of the inc and get functions for each instance, which may impact performance? I don't know.

Upvotes: 3

Views: 8598

Answers (5)

Steven Spungin
Steven Spungin

Reputation: 29109

The biggest difference between the two is that calling new on a class creates a this object, while the modular function may or may not create an instance-like state object.

I am not going to post the advantages of using a class because there are hundreds of them in Stack Overflow. Instead, here is why you might not want to use a class.

By using the modular function instead of a class, you:

  • May get a speed bump. My tests show that not using a class gains 10% speed improvement for basic cases
  • Avoid having to call .this on instance-like properties
  • Avoid having to call Class.[name] on static-like properties
  • Avoid needing to declare a separate constructor
  • Use the closure instead of needing to copy constructor-like parameters
  • Do not need to allocate space for a this object
  • You can easily pass the function around; clients are not required not know if they should call new to invoke it
  • What you return from the class can be a singleton, stateless library, factory, etc.
  • Client code may be cleaner without the new keyword
  • You can refactor to use instance-like state without clients needing to add the new keyword.
  • You can rely on all sorts of closure magic and global state by declaring functions and properties in various scopes within or outside of the function
  • Destructuring and enumerating the properties will be restricted to exactly what you define, without any magic class properties lurking inside.
  • Allows you to use composition instead of inheritance.

In the most concise syntax

() => ({})

With constructor-like params and closure access

(a,b,c) => ({
  someA: a,
  bAndC: b + c
})

A full example

let lastTotal
const randomPct = () => Math.random()*100

function SomeModule(a,b,c) {
  const myTotal = a + b + c
  lastTotal = myTotal
  const getASquared = () => a * a

  return {
    total: a + b + c,
    getTotal() {
       return a + b + c
    },
    myTotal,
    lastTotal,
    randomPct,
    getASquared,
  }
}

A composition example. You have the flexiblity to rename conflicts, or exclude any members of moduleA and moduleB, even conditionally.

const moduleA = useModuleA()
const moduleB = useModuleB()

const moduleC = (moduleA, moduleB) {
  const exportedB = isAdmin() ? moduleB : {publicA: moduleB.publicA, publicB:moduleB.publicB}
  return {
    ...moduleA,
    ...exportedB,
    hello: 'World',
  }
}

Upvotes: 0

Jesse
Jesse

Reputation: 6995

I put it in a jsperf page here to test the speed of new instance creation in either style: https://jsperf.com/class-vs-object-2

Here are the results on my computer (new instances created per second):

Test                    Chrome           Firefox           Edge
new Counter1();    217,901,688     2,086,800,399     31,106,382
Counter2();         30,495,508        36,113,817      9,232,171

I don't know why the Firefox number with class is so high (hopefully it didn't notice the code had no effect and elide the whole thing) or why the Edge numbers are so low.

So at least with regards to performance, a real class is much faster, probably because it avoids the overhead of creating a new copy of the methods for each instance.

While the verbosity and lack of private state with classes is annoying, I think the performance benefit is more important, but it depends on the situation.

Edit

I've made the test call inc() and get() on the resulting object as well and the numbers are basically the same:

Test                   Chrome          Firefox         Edge
new Counter1();   215,352,342    2,072,278,834   23,570,036
Counter2();        14,309,836       35,564,201    9,801,748

Upvotes: 4

0xdw
0xdw

Reputation: 3842

I recommend to use classes, because of that,

  • Class methods are non-enumerable.

    A class definition sets of enumerable flag to false for all class member methods in the "prototype". That's good, because if we for..in over an object, we usually don't want it's class methods.

  • Classes have a default constructor() {}.

    If there is no constructor in the class constructor, then an empty function is generated, same as if we had written constructor() {}.

  • Classes always use strict.

    All code inside the class construct is automatically in strict mode.

  • Classes may also include getters/setters.
  • Static Methods

    We can also assign methods to the class function, not to its "prototype".

You can go further here.

Upvotes: 1

user663031
user663031

Reputation:

Which should I use?

This question is basically a matter of personal preference. True believers in OOP will of course adopt the class approach. The one clear difference is that that would allow you to subclass Counter1, but it's not clear why you would ever want to do that, and even OOP aficionados warn against building overly complex class hierarchies.

Yes, the second alternative does place the methods in question on each and every instance, instead of the prototype. Yes, that does impose some theoretical performance penalty. But this is not something you ever need to worry about, unless you are writing a game with one counter for each of a million objects.

Presumably you are using some kind of transpiler or running in a native ES6 environment. If for some reason you are not, then of course your second alternative has the advantage that it will run in any browser from the last ten years (if you rewrite the methods as inc: function() { count++; }).

Upvotes: 0

Paul Page
Paul Page

Reputation: 9

Neither one should have a very big impact on performance. While classes (Counter1 in your example) technically have slightly better performance, this is only noticeable if you are creating several thousand instances of the class per second (which is probably a bad design choice anyway).

As for other advantages, there are advantages and disadvantages to both. FunFunFunction is a great YouTube channel for learning about JavaScript, and it's where I first learned about factory functions (Counter). The channel owner strongly favors these over using classes.

One of the reasons for this is that you can't get consistent behavior out of the this keyword with the way classes are implemented in JS, as it may refer to something like an HTML element from which a function was called rather than the object that you're expecting it to.

For more details, see this video.

Of course, this is only one side of the argument, and I'm sure there's arguments for using class as well. I prefer factory functions, but in the end you really just need to pick one and roll with it.

Upvotes: 0

Related Questions