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.