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)
(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
}
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')
Now this object a
will have a constructor
property that points back to the constructor Person
.
a.constructor === Person
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__
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'
(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()
It's getting these capabilities from String.prototype
:
x.__proto__ === String.prototype
(__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'
}
(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
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
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
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__
And This will give you the prototype of a string:
''.__proto__
Obvious, they have different prototypes, so this should be false
:
({}).__proto__ === ''.__proto__
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__
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)