1

I'm new to JavaScript, coming from a Python background. I'm currently trying to understand the value of working directly with prototypes rather than classes.

Class Approach

For example, below we have a class for Dog

// Class approach
class Dog1 {
    
    genus = "canis";
    vertebrate = true; 

    constructor(name, breed) {
        this.name = name;
        this.breed = breed;
    }

    bark() {
        console.log("Bark!")
    }

}

Prototype Approach

While the equivalent prototype version would be (I believe)

// Prototype approach
function Dog2(name, breed) {
    this.name = name;
    this.breed = breed;
}

Dog2.prototype.bark = function() {
    console.log("Bark!")
}

Dog2.prototype.genus = "canis"
Dog2.prototype.vertebrate = true

In general, I'm really struggling to see the value of the prototype method.

  1. Adding the method and "class" attribute occurs outside the constructor definition, which seems to inherently makes the code less reusable.
  2. This may be because I am coming from Python, but the class approach just seems inherently cleaner and more intuitive.
    1. For example, why do we have to add bark and genus to Dog2.prototype rather than to Dog2 directly? I assume it is because Dog2 is ultimately a function which is not permitted to have attributes, but which does have a prototype, so we just attach bark and genus to that? But then how can we be assured that the prototype can store attributes?

I know that classes are just syntactic sugar so I can use them, but I want to make sure I'm understanding everything correctly.

.prototype vs .__proto__

I'm also a little confused as to why the prototype attribute of an object doesn't actually point to its prototype, and what the difference is between .prototype and .__proto__. For example, this article has the below diagram for the line function MyConstructor() {}, where the prototype chain(s) are in green:

enter image description here

Is the idea that MyConstructor itself is a function, and so its actual prototype .__proto__ must be what it "subclasses" from, inheriting all function-related functionality, but that it is also a constructor, and so we must define the type of object that it actually constructs (i.e. the class that it is the constructor for), which is what its .prototype object is? So MyConstructor.prototype is the "class", and MyConstructor is the mold for that class that is used to create new instances?

Any advice is greatly appreciated!

WhoDatBoy
  • 329
  • 4
  • 15
  • "*makes the code less reusable*" - did you mean "less readable"? Because the class (constructor) is usable just in the same way. – Bergi Oct 19 '22 at 00:59
  • @Bergi also readable but I did mean reusable. The class method packages everything up nicely into one object, but the prototype defines the constructor and then adds attributes to its prototype separately, so it's not as nicely packaged. E.g. what if the code is edited by someone in the future and that person places lines in between the constructor def and prototype attr definitions that cause unintended side effects. This is not possible in the class approach – WhoDatBoy Oct 19 '22 at 18:09
  • 1
    That's maintainability. Reusability refers to using the same code (the same class object, the same module) for many things, like calling it multiple times from different places. – Bergi Oct 19 '22 at 18:12

3 Answers3

2

The class approach just seems inherently cleaner and more intuitive.

Yes indeed, this is the reason why the class syntax was introduced. It's still (mostly) the same structure though, and writing it without the syntactic sugar can help you understand what's actually going on.

the equivalent prototype version would be (I believe)

Not quite. You forgot the vertebrae property in your second snippet just like you forgot the color property in your first, but the main difference would be that the genus = "canis" class field (in your first snippet) is created on every individual new Dog1 instance, as if it was created in the constructor, whereas the new Dog2 instances created from the second snippet do not have an own .genus property: they all inherit the same property from the shared prototype object. This is comparable to creating an attribute on a Python class object itself, not in the __init__ method for every instance.

Closer to Dog1:

function Dog2(name, breed, color) {
    this.genus = "canis";
    this.vertebrate = true; 
    this.name = name;
    this.breed = breed;
}

Dog2.prototype.bark = function() {
    console.log("Bark!")
};

why do we have to add bark and genus to Dog2.prototype rather than to Dog2 directly? I assume it is because Dog2 is ultimately a function which is not permitted to have attributes

No, a function is an object, and can have properties - just like in Python. It's just that putting a property on the constructor function makes it static (like @classmethod in Python), and the property won't get inherited by the instances.

MyConstructor itself is a function, and so its actual prototype .__proto__ must be what it "subclasses" from, inheriting all function-related functionality, but that it is also a constructor, and so we must define the type of object that it actually constructs [separately], which is what its .prototype object is?

Yes, that sums it up quite nicely. See also __proto__ VS. prototype in JavaScript, How does __proto__ differ from constructor.prototype? and How does JavaScript .prototype work?.

So MyConstructor.prototype is the "class", and MyConstructor is the mold for that class that is used to create new instances?

Yeah, though in JS the terminology is that MyConstructor is "the class" and MyConstructor.prototype is "its prototype".

MyConstructor (also .constructor of the class's prototype object) is comparable to def __init__ or def __new__ in Python.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks for the thorough response! And thanks for pointing out the `vertebrate` and `genus` inconsistencies - I fixed that in the OP. Thanks for the clarification on the `genus` attribute too - that makes perfect sense. Is there any way to mimic the behavior of attaching it to the prototype in the class method? The modified `Dog2` snippet you added seems to mimic the behavior of `Dog1` if I understand. Finally, how does JS "know" that `Dog2` is a constructor and not just a regular function? Is it simply because it returns an object (literal?) ? Thanks again for your insight :) – WhoDatBoy Oct 19 '22 at 18:20
  • 1
    "*Is there any way to mimic the behavior of attaching it to the prototype in the class method?*" - no. Methods (and getters/setters) are created on the prototype. Putting data on the prototype is unusual and often discouraged because it's confusing to people unfamiliar with this style, and has been known to cause bugs if you place a mutable object on the prototype whose state is then shared by all instances. "*How does JS "know" that `Dog2` is a constructor and not just a regular function?*" - it doesn't really. Only when you call it with `new` it becomes a constructor - another downside. – Bergi Oct 19 '22 at 18:27
1

Maybe this would be interesting to read: https://levelup.gitconnected.com/using-prototype-vs-this-in-a-javascript-class-can-help-save-memory-816636418c3e

By the way, in javascript, classes are only syntactic sugar, behind the scenes you're dealing with prototypes.

Since you are novice in javascript, i recommend reading "You don't know JS" book series. If i remember correctly, in one of these book the author explains prototypes and classes.

srkigres
  • 41
  • 3
0

Javascript uses prototypal inheritance.

When you create a new object of type MyObjectType, the object's properties, including methods, come from those defined in MyObjectType.prototype. The instantiated object obj maintains an internal link to MyObjectType.prototype which is used as a fallback to any property or method not explicitly defined (overridden) in the object itself.

This is the case whether you define the contents of MyObjectType.prototype directly, or you use the (newer) class syntax which does this implicitly.

Before the class syntax was added to Javascript, manipulating the prototype property of the constructor object was the only way. The class syntax makes defining something that will act like a class a bit more intuitive, but it doesn't change what is happening under the hood - the constructor object's prototype property is still being populated and used.

The main part of your question seems to be that you don't understand why the prototype method exists, when you can do it using the class syntax. The answer to this is that the class syntax is new, and did not exist for the bulk of Javascript's history.

A secondary part of your question seems to be asking about __proto__ vs the prototype property. From an instantiated object, you can't traditionally work backwards in Javascript to access the object's prototype object, and so implementations added a non-standard __proto__ property to do this, and not only that but to allow modifying the prototype the instantiated object was based on. This was never officially in any spec should be considered deprecated now. You can use Object.getPrototypeOf / Object.setPrototypeOf to do the same now instead. That said, in regular programming there is generally never a need to do this and IMO it can be a bad code smell.

thomasrutter
  • 114,488
  • 30
  • 148
  • 167
  • Hi @thomasrutter - thanks for your reponse. To clarify, the `.__proto__` property of a constructor gives its own prototype, allowing it to include all function-related functionality (since a constructor is a subclass of general functions) Separately, its `.prototype` property defines the prototype *of the objects it constructs*, allowing you to modify the "class template" for objects constructed (and initialized?) with the constructor. Am I understanding this correctly? – WhoDatBoy Oct 19 '22 at 18:25
  • Sort of but your use of the word constructor is not quite right from the looks of it. To me, traditionally, when you say `customer = new Person` "Person" is the constructor object and "customer" is an object instantiated from it which inherits Person.prototype. That said, the new class syntax in Javascript adds a constructor *keyword* allowing definition of a constructor method like in C++/Java. This is a different use of the term constructor in JS. – thomasrutter Oct 20 '22 at 02:27