DEV Community

Andy Li
Andy Li

Posted on • Edited on

Constructor, Prototype, and Their Instance: Relationships

This article is going to use a relationship-oriented approach to dissect the prototypical OOP system of the .. the most popular programming language in the world~

Even if you're using the ES6 class syntax, that's just the surface, constructor and prototype are still doing their thing under the hood. So the prototypical system that I'll be talking about in this article is still relevant today and in the future.

The four relationships I'll get into are:

  • An instance has a constructor
  • An instance has a prototype
  • A constructor has a prototype, a prototype has a constructor
  • A prototype has a prototype (not in the picture)

Alt Text

(has means one thing has a property pointing to the other thing)

1. An instance has a constructor

This is a constructor:

function Person(name){
  this.name = name
}
Enter fullscreen mode Exit fullscreen mode

As you can see, it's basically just a function with a capitalized name.

And you can create new instances of this constructor like this:

const a = new Person('andy')
Enter fullscreen mode Exit fullscreen mode

Now this object a will have a constructor property that points back to the constructor Person.

a.constructor === Person
Enter fullscreen mode Exit fullscreen mode

Each object also has a __proto__ property that points to a prototype. That means, the above object a also has a __proto__ property that points to its prototype.

a.__proto__
Enter fullscreen mode Exit fullscreen mode

But first, let's talk about what a prototype is.

2. An instance has a prototype

A prototype is a place that holds the essential capabilities that the instances of a type can share. These capabilities are usually instance methods.

If a method is called on an object, usually the method isn't stored in the object, it's stored in the object's prototype (or the prototype's prototype etc). All instances of a particular type will have their instance methods stored in the same prototype (or the prototype's prototype etc).

Let's get some hands-on.

Here's a string instance:

const x = 'x'
Enter fullscreen mode Exit fullscreen mode

(as a side note, a string is technically not an object, but when you access its methods, it gets converted into an object temporarily behind the scene, so for the scope of this article, we can just treat a string value as an object)

It has all the String type's capabilities, for example:

x.toUpperCase()
Enter fullscreen mode Exit fullscreen mode

It's getting these capabilities from String.prototype:

x.__proto__ === String.prototype
Enter fullscreen mode Exit fullscreen mode

(__proto__ is a property that exists in all objects and values, it points to a prototype. However, __proto__ is getting deprecated so it might not be available in some browsers, and definitely don't write code that relies on it.)

A cool thing about this is that, we can add new methods to this prototype:

String.prototype.isX = function(){ 
  return this.toString() === 'x'
}
Enter fullscreen mode Exit fullscreen mode

(This is just for illustration. In everyday development, don't add your own methods to built-in types such as String.)

And then this isX method will be magically available to our existing string value:

x.isX() // true
Enter fullscreen mode Exit fullscreen mode

This is how JavaScript's inheritance works. An object (an instance) gets its capabilities from another object (a prototype), which in turn might get its own capabilities from yet another object (that prototype's prototype).

This link between an instance and its prototype is automatically created when the instance is created, so usually you don't have to worry about connecting an object to a prototype.

3. A constructor has a prototype, a prototype has a constructor

Now let's talk about the relationship between constructor and prototype.

A constructor is linked to a prototype. And a prototype is linked to a constructor. They're like the yin and the yang of JavaScript's prototypical OOP.

Look at this:

x.constructor.prototype.constructor.prototype.constructor.prototype
Enter fullscreen mode Exit fullscreen mode

I can keep doing it, but I think you get the idea. There is a circular relationship between an object's constructor and its prototype.

The constructor has a prototype property that points to its associated prototype, and the prototype has a constructor property that points to its associated constructor.

With that in mind, here are some interesting relationships:

// the same constructor
x.constructor === String

// the same prototype
x.__proto__ === String.prototype
x.constructor.prototype === String.prototype
Enter fullscreen mode Exit fullscreen mode

Just be aware that an instance has the __proto__ property, a constructor has the prototype property. Their names are different, but they are pointing at the same prototype.

4. A prototype has a prototype

I mentioned "a prototype's prototype." This sounds complicated, but let's take a look at a simple example.

This will give you the prototype of a plain object:

({}).__proto__
Enter fullscreen mode Exit fullscreen mode

And This will give you the prototype of a string:

''.__proto__
Enter fullscreen mode Exit fullscreen mode

Obvious, they have different prototypes, so this should be false:

({}).__proto__ === ''.__proto__
Enter fullscreen mode Exit fullscreen mode

But since the String type is extending from the Object type. The plain object's prototype is actually the string's prototype's prototype.

So this will be true:

({}).__proto__ === ''.__proto__.__proto__
Enter fullscreen mode Exit fullscreen mode

This __proto__.proto__ thing is called the prototype chain. And the end of the prototype chain is null.

Bottom Line

Between a constructor and a prototype, that's a two-way relationship. Anything from an instance to either its constructor or its prototype, that's only a one-way relationship. There's no arrow pointing back to each instance from a constructor and a prototype.

Catch you later

Top comments (0)