Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should functions be attached to object or its prototype?

I am learning javascript and I am wondering about the concept of functions and prototypes inside an object.

My understanding of the concept

As I understand it, functions should be attached to object's prototype in order to allocate less memory.

For example (if what I said is true), both version will do the same job but second version will allocate less memory:

var collectionOfObjects = [];
for(var i=0; i<1000; i++){
    collectionOfObjects.push({id: 2, sayHi: function(){console .log('hi')}})
}
//vs
function Foobar(id){
    this.id = id || 0;
}
Foobar.prototype.sayHi = function(){
    console.log('hi');
}
var otherCollection = [];
for(var i=0; i<1000; i++){
    otherCollection.push(new Foobar());
}

Question

//this attaches sayHi function to the object instead of its prototype
var foobar = {
    id: 0,
    sayHi: function(){
        console.log('Hi');
    }
}

Since I shouldn't use __proto__, how do I attach sayHi function to foobar's prototype instead of foobar object? I assume that adding sayHi to Object.prototype isn't a good solution as it would add sayHi function to every object.

like image 355
askstackoverflow Avatar asked Feb 25 '20 22:02

askstackoverflow


2 Answers

The prototype chain is setup via the new keyword, a class or Object.create(). Either way, it's set when the object is created.

So you already know one way, which is is to add functions to the prototype of a constructor function and instantiate with new.

The modern way would be to use a class, but you specifically mention legacy code here, so I'm going to assume that's out for purposes of this discussion.

The last way is to use Object.create() which gives you an object with some prototype:

var myProto = {
  // There is only one of this function, ever.
  sayHi: function() {
    console.log('hi');
  }
}

var otherCollection = [];
for(var i=0; i<1000; i++){
    var obj = Object.create(myProto)
    obj.id = i // or other custom setup.
    otherCollection.push(obj);
}

That said, in modern javascript engines having unique functions per object is not a big deal. I've worked on some pretty large javascript apps and memory usage due to the number of function instances has not ever been a performance bottleneck at all.

In modern React with functional hooks, for instance, it would be incredibly cumbersome (if not impossible in some cases) to avoid creating hundreds of new functions on every render. It's designed to be that way and it still performs very well. And if it doesn't perform well, it's never due to too many functions.

like image 190
Alex Wayne Avatar answered Nov 15 '22 11:11

Alex Wayne


You understanding is accurate. Objects created with the obj = { ... } notation inherit from Object.prototype, and changing their prototype with __proto__ or its modern equivalent Object.setPrototypeOf is especially slow in all JavaScript implementations. But there is another way to set the prototype of an object on creation.

The Object.create(proto) function lets you create an object which inherits from proto, so you don't have to use the confusing Foobar.prototype syntax and the magical new operator.

let Foobar = {
  id: 0,
  sayHi: function() {
    console.log('Hi');
  }
};

let obj = Object.create(Foobar);
obj.id = 1;
obj.sayHi(); // Hi

If you had a dozen properties to set on obj, having to assign each property manually after the object creation would be a bit lame, but you can use the nifty Object.assign function for that:

let Elephant = {
  name: 'an elephant',
  ears: 'boring',
  sayHi: function() {
    console.log(`Hi, I am ${this.name} and my ears are ${this.ears}.`);
  }
};

let dumbo = Object.assign(Object.create(Elephant), {
  name: 'Dumbo',
  ears: 'AWESOME'
});

dumbo.sayHi(); // Hi, I am Dumbo and my ears are AWESOME.

If you wanted to create a bunch of objects and give them all the same 3 methods, you could therefore replace this:

let t = { name: "Tom", eat, sleep, repeat };
let g = { name: "Garfield", eat, sleep, repeat };

with this:

let protoCat = { eat, sleep, repeat };
let t = Object.assign(Object.create(protoCat), { name: "Tom" });
let g = Object.assign(Object.create(protoCat), { name: "Garfield });

It would be more practical, however, to have some kind of constructor function instead. Using Object.assign for a single property is a bit overkill so I'll simplify this next example:

function cat(name) {
    const c = Object.create(protoCat);
    c.name = name;
    return c;
}

let t = cat("Tom");
let t = cat("Garfield");

Note that you don't call cat with new, since cat builds its own object. If you tried calling cat with the new operator here, it would create an object which inherits from the empty cat.prototype, but that object would eventually be discarded since cat has a return value.

UPDATE: While my answer gives a pretty complete explanation of how prototype works, I feel like I failed to point out that you don't have to use it at all. You could very well just assign the methods every time.

function cat(name) {
    return { name, eat, sleep, repeat };
}

Sure, all your cats now hold a reference to all their methods, which might take a tiny bit of extra memory, but writing hard to maintain code to save a few bytes isn't what I'd call an optimization.

JavaScript likes to treat objects like data, in a way similar to lisp, and if you just pretend it has classes, you might end up wishing you were using another language.

like image 32
Domino Avatar answered Nov 15 '22 10:11

Domino