DEV Community

Discussion on: ELI5: Functions vs. Class/Constructor in Javascript

Collapse
 
adrianmarkperea profile image
Adrian Perea • Edited

I'll give you the low-down on classes in JavaScript and classes in traditional OOP languages, say Python.

Traditional OOP languages

In traditional OOP languages, a class is a blueprint for objects. In terms of information management, the class is used to provide a) abstraction (which information is relevant? which is irrelevant?) and b) encapsulation (how do I hide/show what is relevant/irrelevant?). Through a class, I can create multiple implementations of this blueprint. These implementations are called instances. Each instance gets a copy of the methods and instance variables of the class.

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def print_full_name(self):
        print(f'{self.first_name} {self.last_name}') # Adrian Perea        

person_a = Person('Adrian', 'Perea')
person_b = Person('G', 'Nio')

person_a.print_full_name() # Adrian Perea
person_b.print_full_name() # G Nio
Enter fullscreen mode Exit fullscreen mode

In this example, person_a and person_b are instances of the blueprint (read: class) Person. Each of them gets its own first_name and last_name instance variables, and print_full_name method.

JavaScript

Now JavaScript, while seemingly does the same thing, does this differently. Whenever we use the new keyword to execute a function, we create an implicit object that we can reference with this inside the function. Furthermore, objects created with a constructor function have a reference to the constructor function inside their own prototypes. That was a lot. Let's try to unpack this with code.

Let's first make an object without any fancy constructor functions:

function Person(first_name, last_name) {
    return {
        first_name,
        last_name,
        fullName() {
            console.log(`${this.first_name} ${this.last_name}`)
        }
    }
}

const person_a = Person('Adrian', 'Perea')
const person_b = Person('G', 'Nio')

peron_a.fullName() // Adrian Perea
person_b.fullName() // G Nio
Enter fullscreen mode Exit fullscreen mode

This works fully well. Why not call it a day and be done with it?
The brutally honest truth is, we can call it a day and be done with it. There are a lot of use cases that you can accomplish by just creating objects this way. But just because we can, doesn't mean we should, especially if there are arguably better ways of accomplishing the same thing. If we proceed with this route, we're not taking advantage of the prototypical inheritance that makes JavaScript unique (not necessarily better nor worse).

Let's take a look at another way we can implement this:

function Person(first_name, last_name) {
    this.first_name = first_name;
    this.last_name = last_name;
}

Person.prototype.fullName = function() {
    console.log(`${first_name} ${last_name}`);
}

const person_a = new Person('Adrian', 'Perea');
const person_b = new Person('G', 'Nio');

person_a.fullName() // Adrian Perea
person_b.fullName() // G Nio
Enter fullscreen mode Exit fullscreen mode

As I mentioned earlier, there are two things that are happening whenever we use the new keyword.

  1. An implicit object is created and is referred to as this. So, when we do this.firstName or this.lastName, it's as if we're assigning properties to an empty object.

  2. Objects created with a constructor function have a reference to the constructor function inside their own prototypes. After creating the constructor function above and its instances, try to type the following in your browser console:

person_a.__proto__ === Person.prototype // true
Enter fullscreen mode Exit fullscreen mode

Whenever we create a new instance of the Person constructor function through the new keyword, a reference to the prototype object of the constructor function gets added to the __proto__ property of the object. Read that again. After that, read it one more time. This bit is important.

As opposed to traditional OOP languages, methods are not copied to each instance of the class. When we call person_a.fullName(), JavaScript climbs up the __proto__ properties of the instances until it finds a qualified name of fullName(). Therefore, the method fullName() lives completely inside the prototype of the constructor function. This provides performance benefits since the methods only have to be defined once (on the prototype). That is, person_a.fullName === person_b.fullName === Person.prototype.fullName.

This means that whatever we define on Person.prototype will be available to all instances of Person. So we can do something weird (in traditional OOP sense) like this:

Person.prototype.lastName = function() {
    console.log(this.lastName);
}

// Note that we did not recreate the objects here!
person_a.lastName() // Perea
person_b.lastName() // Nio
Enter fullscreen mode Exit fullscreen mode

I hope that this clears everything up. To summarize:

  • constructor functions do two things: create an implicit object that is referenced by this, and assign the __proto__ property of each instance as a reference to the prototype object of the constructor function
  • When a function is called on the instance, the prototype chain is climbed until a reference to the function is found
  • In traditional OOP, all instances have a copy of each method. There is no concept of prototype

I didn't include a bit about inheritance. But I think that this information is enough to get you to understand it on your own.

A note about ES6 "classes"

Syntactic sugar. It's quite cumbersome to write prototype for each method you want to share amongst instances (in addition to breaking encapsulation at the textual level). ES6 class syntax is just an easy way to store everything in one place, without having to mess with prototype and all of that jazz.

I hope this was helpful.

Cheers,
Adrian

Collapse
 
danielkun profile image
Daniel Albuschat

Adrian, nice! 👍 I once read an entire book in order to learn what you just covered in this comment.
Feel free to paste this comment into a whole new post, since it sure is worth it's own post on DEV!

Collapse
 
adrianmarkperea profile image
Adrian Perea

Wow! Thanks for the nice comment, Daniel! Working on its own post right now. Look out for it!

Collapse
 
gnio profile image
Gnio

Thank you so much for taking the time to write this reply. As Daniel mentioned, this deserve its own post. I'm sorry I didn't reply before, but I live among persistent distractions and internet issues. Thank you so much. I'm reviewing it now!

Collapse
 
gnio profile image
Gnio

Years later this response have helped me so much. Thank you so much Adrian.