2

Let's say I want to create an array of Person using a random data library.

I could do something like

import {generateRandom} from 'someLib'

let people = []

function getPerson() ({
  name: generateRandom.string()
  age: generateRandom.number()
})

for (let i = 0; i < 10; i++) {
  people.push(getPerson())
}

But I could also do something like

import {generateRandom} from 'someLib'

class Person {
  constructor() {
    this.name = generateRandom.string(),
    this.age = generateRandom.number()
  }
}

let people = []

for (let i = 0; i < 10; i++) { 
  people.push(new Person()) 
}

On a memory level, is there any difference in the outcome?


(This is just a theoretical question, I am not trying to solve any problem in particular)

I have found this question that is related to this Difference between creating a class in javascript to create an object and creating an class and object in Java

Which states that there are no classes in JS.

Is this just syntactic sugar? 2 ways of doing exactly the same thing?

Michael Vigato
  • 326
  • 2
  • 11
  • 2
    This may be a theoretical question but it's useless without a concrete context. The code you have is not really representative. If you had methods on the instance and/or inherited properties, then a class will be more efficient, as you won't need to re-create each of these on each instance. But a plain object might work better in other cases. Even then, chances are that in many other cases there would be no meaningful difference. Moreover, the memory footprint may depend on the engine you run the code. Different context and optimisations might change that. – VLAZ Dec 02 '22 at 08:40
  • I am not interested in which of the 2 syntax is better in a given context, or which is more efficient: I am wondering if the outcome of these 2 pieces of code are exactly the same under the hood (i.e. 2 different syntax to achieve the exact same result), or if they differ – Michael Vigato Dec 02 '22 at 08:46
  • 2
    That's a different question than what's in the title. – VLAZ Dec 02 '22 at 08:47
  • Some docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes it's essentially the same but classes have built in features – Chris Hamilton Dec 02 '22 at 08:57
  • 3
    Do you realize that in your first code block `getPerson()` isn't even shown with valid syntax (it results in `SyntaxError` when run) and doesn't even attempt to return the object you created from the function. – jfriend00 Dec 02 '22 at 09:44
  • @MichaelVigato You'd have to look at engine source code IMO. Classes provide an alternative way to coordinate object instantiation, most of which is simply an encoding of specific steps that could be done without classes. But classes also provide features that are unique to classes and have no equivalent in ES5-style code. – Ben Aston Dec 02 '22 at 10:04
  • 1
    " I am wondering if the outcome of these 2 pieces of code are exactly the same under the hood" That is *implementation dependent*, even if we can tell you whether that is true, *today*, for a given implementation, there's no guarantee it will stay that way. – Jared Smith Dec 02 '22 at 19:50
  • 2
    Why are you even asking for "the memory level"? The two codes are not even equivalent at the javascript level, creating different objects that inherit different properties from different prototypes. – Bergi Dec 02 '22 at 19:53

2 Answers2

5

If you want to examine the memory yourself, you can put this code in the console:

class Test{};

const test = new Test();

class Class {};
test.Class = Class;
test.classObj = new Class();

function func() {return {};};
test.func = func;
test.funcObj = func();

Take a heap snapshot with google chrome dev tools, sort by constructor and find the Test class.

You can then examine the memory of these functions and objects. Here's a screenshot of what I got:

1st results

You can see that the object instantiated by the class constructor is slightly larger than the one instantiated by the function. Expanding the prototype, you can see they both use the Object constructor, but the classObj has the additional Class constructor in its prototype chain.

You can also see that the class constructor appears to retain more memory than regular functions, retained size meaning memory that will be cleaned up by garbage collection if the function is no longer in use.

Seems to be an extra 172 bytes for the class constructor, and 24 bytes per object for an empty object.


Following VLAZ's comment, here's the results with 10 methods added, and 10 instances.

class Test{};

const test = new Test();

class Class {
  method0(){};
  method1(){};
  method2(){};
  method3(){};
  method4(){};
  method5(){};
  method6(){};
  method7(){};
  method8(){};
  method9(){};
};
test.Class = Class;
for (let i=0; i < 10; i++){
  test["classObj" + i] = new Class();
}


function func0(){};
function func1(){};
function func2(){};
function func3(){};
function func4(){};
function func5(){};
function func6(){};
function func7(){};
function func8(){};
function func9(){};
function constructorFunc() {
  return {
    method0: func0,
    method1: func1,
    method2: func2,
    method3: func3,
    method4: func4,
    method5: func5,
    method6: func6,
    method7: func7,
    method8: func8,
    method9: func9,
  };
};
test.constructorFunc = constructorFunc;
for (let i=0; i < 10; i++){
  test["funcObj" + i] = constructorFunc();
}

all objects

expanded Class

The shallow size of the class objects are now much smaller. This seems to be due to the fact that they can just store a reference to the class prototype rather than reference all of their methods directly.

At first glance, the retained size of Class seems to be smaller than constructorFunc, but expanding Class you can see a property named prototype which is an object retaining an extra 1.38 KB. Adding that to the 520 B of the class itself pushes it above the retained memory of constructorFunc. But the memory saved by creating instances of the class instead of an object will outweigh that pretty quick.

So seems like classes are the way to go.

Chris Hamilton
  • 9,252
  • 1
  • 9
  • 26
  • 1
    "*I believe classes will always be larger though.*" no. Try a class with several methods, then make 10 instances of the class and 10 objects. Compare the sizes. – VLAZ Dec 02 '22 at 12:06
  • 1
    @VLAZ I've posted the results, thanks. Feel free to add any extra explanation. – Chris Hamilton Dec 02 '22 at 20:27
1

Your first getPerson() is invalid syntax and the function does not even attempt to return anything (if it was valid syntax). So, people.push(getPerson()) would generate an array of undefined(if the syntaxError was fixed) which will be entirely different than your second code block which generates an array of objects.


If, what you meant to ask about was something like this:

let people = []

function getPerson() {
  return {
    name: generateRandom.string()
    age: generateRandom.number()
    }
}

for (let i = 0; i < 10; i++) {
  people.push(getPerson())
}

Then, this and your class will each create an array of objects and those objects will each have your two properties on them. The Class will contain some additional setup on the prototype that allows for subclassing, but if you're just asking about using the object with two properties, then these two would fundamentally do the same thing.

On a memory level, is there any difference in the outcome?

Without methods, your two properties will occupy the same amount of memory. The two properties are assigned directly to the object so those two properties use the same memory.

The class will actually set up a few other things on the object like a .constructor property and the object will get its own separate prototype. The prototype is shared among all instances. So, there could be slightly more memory usage for the class instance, but it's unlikely this makes a material difference.

If you defined methods directly on the object in the first implementation and methods in the class in the second implementation, then the class would definitely be more efficient because there would be one copy of the methods on the prototype vs. many separate copies of the methods on each instance of the object. But, you didn't show that in your question.

Which states that there are no classes in JS. Is this just syntactic sugar? 2 ways of doing exactly the same thing?

Classes are syntactic sugar, but they make it easy to define things in an efficient way without having to do a bunch of manual things. And, because the language sets things up for you, then everyone using a class definition creates code that works the same way.

You can manually build the same object that instantiating an instance of a class does, but it's a lot more code to do write all the bookkeeping that a class does for you. Your simple object with just two instance properties doesn't show any of that or need any of that, but when you start having methods and sub-classing things and overriding base methods and then calling base methods in your derived implementation, the class definition takes care of a some bookkeeping for you and simply lets you write, good extensible code faster and with everyone doing it the same way. Again, your simple object with just two properties does not show or need what a class does, but other uses of objects do.

For example, the class definition and new Person() creates an object that automatically sets yourObject.constructor to be the constructor that created the object. This allows other code to know what type of object it is or allows other code to abstractly create new instances of the same object. Methods are put on the prototype of the object and made non-enumerable. Constructors can call super(...) to execute the base class constructor (whatever it happens to be). Without the class definition, you have to do that very manually by calling the exact right function in the base class.

jfriend00
  • 683,504
  • 96
  • 985
  • 979