7

I've read a ton of materials about prototypes and understand inheritance in general. However, this is one thing that is bugging me and I cannot figure it out.

On dmitrysoshnikov.com there is a simplified example of how prototypal inheritance could be achieved with the following snippet:

// Generic prototype for all letters.
let letter = {
  getNumber() {
    return this.number;
  }
};
 
let a = {number: 1, __proto__: letter};
let b = {number: 2, __proto__: letter};
// ...
let z = {number: 26, __proto__: letter};
 
console.log(
  a.getNumber(), // 1
  b.getNumber(), // 2
  z.getNumber(), // 26
);

Which follows with this diagram

Figure 1

However, when we start to use the actual inheritance constructions (with the new keyword) it starts to look like this:

Figure 2

I understand how it works. What I don't understand is why suddenly we need the Letter.prototype object from which all the child instances inherit from instead of having it like the first diagram above. To me, it didn't seem like anything was broken with the first example.

One potential reason I can think of is that the actual way allows implementing static methods/properties in classes. In the example above if you'd add a static method then it would be a function added to the Letter object but not Letter.prototype object. The child objects (a,b,z) would have no access to that function. In the first example, this kind of feature would have to be implemented differently but I still don't think that's a good enough reason to create the new Prototype object. I think this static methods feature could be implemented without it.

Am I missing something?

EDIT:

I think there is a lot of people trying to explain things which I'm grateful but I'm not sure my question of WHY was javascript runtime designed to behave one way instead of another properly understood.

To show what I mean here is a few things I tried.

class Car{
     method() {
         console.log("hello")
     }
 }

 myCar = new Car();

// First a few tests as expected
myCar.method() // works
console.log(myCar.method === Car.method) // False, JS doesn't work that way, ok...
console.log(myCar.method === Car.prototype.method) // This is how it works, fine...

// How about we move the reference to the method up one level
Car.method = Car.prototype.method

// Delete the reference to it in prototype object,
// Btw. I tried to remove reference to whole prototype but somehow doesn't let me
delete Car.prototype.method

// Change the prototype chain so it links directly to Car and not Car's prototype object
myCar.__proto__ = Car

myCar.method() // Still works!!!
console.log(myCar.method === Car.method) // True !
console.log(myCar.method === Car.prototype.method) // False, we deleted the method property out of Car.prototype

So, Car.prototype is not needed anymore, at least not for myCar's execution. So why does the method go inside Car.prototype and not Car and then why not myCar.__proto__ = Car instead of myCar.__proto__ = Car.prototype?

md2312
  • 121
  • 1
  • 1
  • 5
  • 1
    The doesn't seem to be inheriting anything, it's pure violence. You should not access `__proto__`, use a constructor function or `Object.create` or `class` to make objects to inherit other objects. – Teemu Dec 07 '20 at 12:28
  • 1
    Consider avoiding the use of `__proto__`. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto – evolutionxbox Dec 07 '20 at 12:30
  • Yeah I understand that `__proto__` is not to be used. I just copied a snippet from the other website which demonstrates how it internally works. I know that Object.create would achieve the same. – md2312 Dec 07 '20 at 12:51
  • In real life I would just use the `class` syntax with the `new` keyword for intantiting objects. However, I still don't understand why when you create a class Foo, it need to create two objects, one for Foo itself and one for Foo.prototype. Why the JavaScript designers decided that just Foo is not enough and you also need Foo.prototype before you have other objects linking to it (like the first diagram above). – md2312 Dec 07 '20 at 12:53
  • The first diagram/snippet above is a hack but it shows code reuse which essentially is inheritance without adding the prototype object. The actual inheritance (with the likes of `class` and `new`) seem to be overcomplicating it with the prototype object and I'm just trying to understand the WHY and not HOW. – md2312 Dec 07 '20 at 13:01
  • 2
    We can't answer that question. You've to ask Brendan Eich about the essentials behind the design decisions, or maybe it's hidden in the [history](https://dl.acm.org/doi/pdf/10.1145/3386327). – Teemu Dec 07 '20 at 13:55
  • Does this answer your question? [What’s the purpose of prototype?](https://stackoverflow.com/questions/8433459/what-s-the-purpose-of-prototype) – Liam Dec 07 '20 at 14:09

3 Answers3

4

I don't understand is why suddenly we need the Letter.prototype object from which all the child instances inherit from instead of having it like the first diagram above.

Actually nothing changed there. It's still the same object with the same purpose as the object named const letter in the first example. The letter instances inherit from it, it stores the getNumber method, it inherits from Object.prototype.

What changed is the additional Letter function.

To me it didn't seem like anything was broken with the first example.

Yes, it was: {number: 2, __proto__: letter} is a really ugly way of creating an instance, and doesn't work when having to execute more complicated logic to initialise the properties.

An approach to fix this issue is

// Generic prototype for all letters.
const letterPrototype = {
  getNumber() {
    return this.number;
  }
};
const makeLetter = (number) => {
  const letter = Object.create(letterPrototype); // {__proto__: letterPrototype}
  if (number < 0) throw new RangeError("letters must be numbered positive"); // or something
  letter.number = number;
  return letter;
}
 
let a = makeLetter(1);
let b = makeLetter(2);
// ...
let z = makeLetter(26);
 
console.log(
  a.getNumber(), // 1
  b.getNumber(), // 2
  z.getNumber(), // 26
);

Now we have two values, makeLetter and letterPrototype that somehow belong to each other. Also, when comparing all kinds of make… functions, they all share the same pattern of first creating a new object inheriting from the respective prototype, then returning it at the end. To simplify, a generic construct was introduced:

// generic object instantiation
const makeNew = (prototype, ...args) => {
  const obj = Object.create(prototype);
  obj.constructor(...args);
  return obj;
}

// prototype for all letters.
const letter = {
  constructor(number) {
    if (number < 0) throw new RangeError("letters must be numbered positive"); // or something
    letter.number = number;
  },
  getNumber() {
    return this.number;
  }
};
 
let a = makeNew(letter, 1);
let b = makeNew(letter, 2);
// ...
let z = makeNew(letter, 26);
 
console.log(
  a.getNumber(), // 1
  b.getNumber(), // 2
  z.getNumber(), // 26
);

You can see where we're going? makeNew is actually part of the language, the new operator. While this would have worked, what was actually chosen for the syntax is to make the constructor the value being passed to new and the prototype object being stored on .prototype of the constructor function.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks for replying. I think I get how `new` keyword works and I think I follow everything you're saying. My question is more of why was there was a need for `.prototype` property&object when designing JavaScript when I'm still seeing it that the methods could be kept one level up. i.e. why not design it so a.getNumber === letter.getNumber. I've added an edit in my original post to clarify some of my thinking – md2312 Dec 07 '20 at 15:03
  • 2
    You mean why not make the constructor and the prototype object into the same entity? Because the constructor function is a function inheriting methods from `Function`, and your instances should not transitively inherit those. It does indeed make sense to keep two distinct prototype chains for "static" class methods and instance methods. – Bergi Dec 07 '20 at 15:29
  • Sure, it would absolutely have made sense to make the prototype object the "main" object not the constructor one, so you'd write classes more like in my second snippet, but it just wasn't chosen. There are other languages that follow this approach, JS went for a pattern that has "classes". – Bergi Dec 07 '20 at 15:31
  • > You mean why not make the constructor and the prototype object into the same entity Yes! The inheritance from `Function` is a good argument I guess. I think this could be the culprit because I think the feature of static methods vs instance methods could have been implemented regardless whether they are the same entity or not. – md2312 Dec 07 '20 at 15:51
1

To me it didn't seem like anything was broken with the first example.

It's not (objectively) and certain people (like Douglas Crockford) have often advocated to avoid .prototype and this all together and use Object.create (similar to your __proto__ example).

So why do people prefer to use classes, inheritance and .prototype?

Prototypes are all about reuse

The reason you typically prototypes is to reuse functionality (like getNumber above). In order to do that it's convenient to use a constructor.

A constructor is just a function that creates objects. In "old" JavaScript you would do:

function Foo(x) { // easy, lets me create many Xs
  this.x = x;
}
// easy, shares functionality across all objects created with new Foo
Foo.prototype.printX() {
  console.log(this.x); 
}
// note that printX isn't saved on every object instance but only once
(new Foo(4)).printX();

ES2015 made this even easier:

class Foo { // roughly sugar for the above with subtle differences.
  constructor(x) { this.x = x; }
  printX() { console.log(this.x); }
}

So to sum it up: you don't have to use .prototype and classes, people do so because it is useful. Note the prototype chain is as large in both examples.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Thanks, what I'm struggling to understand why in your first example it wasn't design to be able to do something like this ` Foo.printX() { console.log(this.x); }` – md2312 Dec 07 '20 at 12:35
  • So directly assigning the printX method on the Foo object and not the Foo.prototype so that new keyword would create an new Object which has a __proto__ (or [[Prototype]]) pointing directly to Foo – md2312 Dec 07 '20 at 12:39
  • @md2312 Foo is a function, Foo has its own prototype (of functions) and giving it a property would have zero impact on objects created using Foo as a constructor. The way it works is (sorry, not my fault): "When you create an object using a function as a constructor - its `.prototype` is assigned as the `__proto__` of the created object" – Benjamin Gruenbaum Dec 07 '20 at 13:01
  • Thanks for replying again. Yes, I understand that it has zero on objects created using Foo as a constructor because it was designed like that. I'm trying to understand WHY it was designed like that, not how it works. To me it would be an easier syntax if you could have methods added as a property directly on the constructor function object kind of like the first diagram in the original post. – md2312 Dec 07 '20 at 13:08
  • @md2312 `__proto__` isn't actually part of JavaScript, for the longest time it wasn't part of the language and it's still part of something called "Annex 2" (and certain environments don't actually implement it). `Object.create` is relatively new too and creating prototype chains used to be _only_ possible with .prototype. See [this](https://crockford.com/javascript/prototypal.html) from ±15 years ago. – Benjamin Gruenbaum Dec 07 '20 at 13:12
  • As a fun tidbit there was some (stalled) discussion to actually make it a (normative) part of JavaScript when TC39 met 3 weeks ago https://github.com/tc39/ecma262/pull/2125. – Benjamin Gruenbaum Dec 07 '20 at 13:14
  • I understand that `__proto__` is not a part of language. It's just the only way to modify the prototype chain yourself in a bit more explicit way. Even if `__proto__` was not a thing then there still is some internal property in child objects to link it up the chain. Btw. I added an edit in my original post to show the kind of things I thought JS could be doing if it was designed differently – md2312 Dec 07 '20 at 15:11
  • @md2312 you can modify it with `Object.setPrototypeOf` and `Object.getPrototypeOf` :] – Benjamin Gruenbaum Dec 07 '20 at 19:25
1

Prototypes are the foundation of JavaScript. They can be used to shorten the code and to reduce memory consumption significantly. Prototype makes also possible to control the inherited properties, and dynamically change existing properties, and add new properties to all the instances created from a constructor function without updating every instance one by one. They can also be used to hide properties from iterations, and prototype helps to avoid naming conflicts in a large object.

Memory consumption

I made a super simple practical example at jsFiddle, it uses jQuery, and is looking like this:

HTML: <div></div>

JS: const div = $('div'); console.log(div);

If we now peek to the console, we can see jQuery has returned an object. That object has 3 own properties and 148 properties in its prototype. Without the prototype, all those 148 properties should had been assigned as own properties to the object. Maybe that's yet a bearable memory load for a single jQuery object, but you might create hundreds of jQuery objects in a relatively simple snippet.

But, those 148 properties are just starters, open the logged tree from its very first property 0, there are a lot of more own properties for the queried div element, and at the end of the list, a prototype, HTMLDivElementPrototype. Open it, and you'll find a couple of properties, and again a prototype: HTMLElementPrototype. Open that, a long list of properties is exposed, and ElementPrototype at the end of the list. Opening that reveals again a lot of properties, and a prototype named NodePrototype. Open that in the tree, and go through the list at the end of that prototype, there's yet one more prototype, EventTargetPrototype, and finally, the last prototype in the chain is Object, which also has some properties in it.

Now, some of all these revealed properties of a div element are objects themselves, like children, which has a single own property (length) and some methods in its prototype. Luckily, the collection is empty, but if we had had added a couple of div elements inside the original, all the properties listed above would have been available for each child of the collection.

If there were no prototypes, and all the properties would be own properties of objects, your browser would still work on that single jQuery object when you've reached this point in this answer when reading. You can just imagine the work load when there are hundreds of elements collected to the jQuery object.

Object iteration

How does a prototype help in iterations then? JavaScript objects have a concept of own properties, that is, some of the properties are added as own properties, and some of the properties are in __proto__. This concept makes it possible to store the actual data and the metadata into the same object.

While with the modern JS it's trivial to iterate through the own properties, we've Object.keys, Object.entries etc, that's not always have been the case. In the early days of JS, there was only for..in loop to iterate through the properties of an object (in very early days there was nothing). With in operator we're getting also properties from the prototype, and we've to separate the data from the metadata with hasOwnProperty check. If everything were in own properties, we couldn't have made any separation between the data and the metadata.

Inside a function

Why the prototype is a property of functions only then? Well, it kinda isn't, functions are also objects, they just have an internal [[Callable]] slot, and a very special property, executable function body. As any other JS type has a "locker" for the own properties and the prototype properties, a function has that third "locker", and a special ability to receive arguments.

The own properties of functions are often called static properties, but they're as dynamic as the properties of regular objects. The executable body of the function and the ability of receiving arguments makes a function ideal for creating objects. Just compare to any other object creation method in JS, you can pass parameters to the "class" (= constructor function), and make very complex operations to get a value for a property. Parameters are also encapsulated inside the function, you don't need to store them in the outer scope.

Both of these advantages are not available in any other object creation operation (you can of course use an IIFE ex. in an object literal, but that's somewhat ugly). Furthermore, the variables declared inside a constructor are not accessible outside of the function, only methods created inside the function can access these variables. This way you can have some "private fields" in the "class".

The default properties of function and shadowing

When we examine a newly-created function, we can see it has some own properties in it (s.c. static properties). These properties are marked as non-enumerable, and hence they're not included in any iterations. The properties are arguments <Null>, caller <Null>, length <Number>, name <String> and prototype <Object> containing constructor <Function>, and the underlying prototype <Function> of the function itself.

Wait! There are two separate properties with the same name in the function, and even with different types? Yes, the underlying prototype is the __proto__ of the function, the other prototype is an own property of the function, which shadows the underlying prototype. All objects have a mechanism of shadowing a property of the __proto__, when a value to a property with the same name existing in __proto__ is assigned. After that, object itself can't access that property in __proto__ directly, it is said to be shadowed. The shadowing mechanism preserves all the properties, and this way handles some naming conflicts. The shadowed properties are still accessible by referring them via the prototype.

Controlling inheritance

As prototype is an own property of the function, it's free booty, you can replace it with a new object, or edit it as you wish, making so doesn't have an effect to the underlying "__proto__", and won't conflict with the "untouched __proto__" principle.

The power of the prototypal inheritance lies exactly on the ability to edit or replace the prototype. You can choose what you want to inherit, and you can also choose the prototype chain, by inheriting the prototype object from other objects.

Creating an instance

How the object creation with a constructor function works, is probably explained thousand times in SO posts, but I put a brief abstract here once again.

Creating an instance of the constructor function is already familiar. When calling a constructor with new operator, a new object is created, and put to the this used inside the constructor. Every property assigned with this becomes to own property of the newly-created instance, and the properties in prototype property of the constructor are shallow copied to the __proto__ of the instance.

This way all the object(ish) properties are preserving their original reference, and actual new objects are not created, just the references are copied. This provides the ability to throw the objects around without need to re-create them every time when they are needed in some other object. And when linked like this, it makes also dynamic edits possible to all the instances at once with a minimum effort.

prototype.constructor

What is the meaning of the constructor in prototype of the constructor function then? That function originally refers to the constructor function itself, it's a circular reference. But when you make a new prototype, you can override the constructor in it. A constructor can be taken from another function, or omitted alltogether. This way you can control the "type" of the instance. When checking whether an instance is an instance of a particular constructor function, you use instanceof operator. This operator checks the prototype chain, and if it finds a constructor of another function from the chain, it is considered as a constructor of the instance. This way all the constructors found from the prototype chain are kinda constructors of the instance, and the "type" of an instance is any of those constructors.


Without a doubt, all this could have been achieved with some other design as well. But to answer the question "why", we need to dive in the history of JS. A recently published book by Brendan Eich and Allen Wirfs-Brock sheads some light to this question.

Everyone agreed that Mocha would be object-based, but without classes, because supporting classes would take too long and risk competing with Java. Out of admiration of Self, Eich chose to start with a dynamic object model using delegation with a single prototype link.

Cite: Page 8 in JavaScript: The First 20 Years, written by Brendan Eich and Allen Wirfs-Brock.

A more deep explanation and background can be gotten by reading the book.

The code part

In your edit there were some questions arised in the comments of the code. As you've noticed, ES6 class syntax hides a regular constructor function. The syntax is not totally only syntactic sugar over the constructor functions, it also adds a more declarative way to create a constructor, and is also capable to subclass some native objects, like Array.

  • "JS doesn't work that way" Correct, method is not an own property of the class (= function).
  • "This is how it works" Yes, the methods created in a class are assigned to the prototype of the class.
  • "Delete the reference to it in prototype object" Not possible, because the prototype is freezed. You can see this by revealing the descriptors of the class.
  • The rest of the code ... No comment, not recommended.
Teemu
  • 22,918
  • 7
  • 53
  • 106