Let's talk about prototypes in JS
Global prototypes
Firstly you should remember that JS has a list of global prototypes, everything is built from them.
For example if you want to get the current date, you will probably use new Date() but what is the word Date? It's a global prototype.
Here are others which you know:
But what do these prototypes mean? Why do we need them?
It is part of the language, when you create a new data (or libraries create some data instead of you), it means that JS uses this global prototype to create some data for us.
Let’s look how it looks in simple examples:
// Left side what we do ===> right side, how JS sees it
// for numbers (primitives):
const age = 21 ===> const age = new Number(21)
// for array (objects):
const languages = [“JS”, “TS”, “Java”, “C++”] ===>
const languages = new Array(“JS”, “TS”, “Java”, “C++”)
Perhaps you have a question: How can we call new Number if it's a primitive? But it's okay, JS creates temporary wrappers for all primitives except undefined and null.
You can create new data by using new {name of prototype} as well but we use left side as it looks easier.
Let’s imagine that you would like to check if a “JS” element exists in the array above, of course we will use .includes or .indexOf methods but how do they appear?
There is logic in inheritance. Our array has a hidden [[Prototype]] field. Every created data has such field, it’s important! When you create a new data, corresponding global prototype will be used to add in the [[Prototype]] field.
For arrays, it’s the Array. For numbers, it’s Number, etc.
So languages array is inherited from global prototype Array. Every global prototype has embedded methods and fields which help us to work with corresponding data. For example Array has .includes and .indexOf fields, by this reason we can check if “JS” element exists in the array.
Nota Bene: Every created data has [[Prototype]] field and all global prototypes inherit from Object prototype but Object from null.
Let's get the languages array into the console and analyze the result:
So when JS can not find some fields/methods in current object, it is going to object from [[Prototype]] field and try to find in this place, etc.
When we use .includes method, it means that JS tries to find this method in the languages array, of course it doesn't exist, so JS is going to [[Prototype]], [[Prototype]] is Array (in current case), so JS can find easily such method and invoke. If there are no necessary fields/methods, then undefined will be returned.
[[Prototype]] helps us to use all the power of JS by using embedded methods and expanding objects. Imagine you have one object and you would like to expand it by another one, it’s time to set something in [[Prototype]] field. This is also how class extensions work. Ok but how to expand the object?
It’s time to talk about __proto__
So __proto__ is getter/setter for [[Prototype]] field but it’s outdated, it’s better to use:
领英推荐
Nota Bene:
We can assign some object to the [[Prototype]] field of another object and thus expand the other object, this is inheritance.
Check the code and screenshot below how I have expended developer object by person object.
const person = {
name: "John",
age: 22
}
const developer = {
language: "Javascript"
}
// person expends developer
Object.setPrototypeOf(developer, person)
console.log(developer) // Result of console below
Here we have the same logic as above. If we try to get name property from developer object. At first Js will try to find it in developer object but as it doesn't exist JS will go to [[Prototype]] object (person object) and then will find name property.
What is F.prototype?
Do you remember the constructor function?
It’s function:
function Person(name, age){
this.name = name;
this.age = age
}
const john = new Person("John", 22)
The idea of this function is create a new object but how can we set the [[Prototype]] field?
We should refer to this constructor function and assign a specific object to the .prototype field
Nota Bene: It's important that .prototype field is default/regular field, not [[Prototype]]. Imagine that you are setting up how the prototype will work in your constructor function by .prototype field.
function Person(name, age){
this.name = name;
this.age = age
}
const defaultActions = {
go: () => console.log('GO'),
sleep: () => console.log('SLEEP'),
eat: () => console.log('EAT')
}
// Here set up which object will be used for [[Prototype]] field in new object
Person.prototype = defaultActions;
// Here a new object is being created with defaultActions object like [[Prototype]]
const john = new Person("John", 22)
console.log(john)
And finally...
..., if you are asked at the interview how to add a new field to an existing global prototype, you can easily do it with .prototype field.
For example, you need to get the first letter from the string. You access the required global prototype (in this case: String), add the name of a new method (in this case: getFirstLetter) through the .prototype and use a new function declaration to declare this, then you make any changes with this keyword and return a new value. You're done !!!
String.prototype.getFirstLetter = function(){
return this.charAt(0)
}
console.log("abc".getFirstLetter())
If this post was useful for you, please like and repost it, thaaanks, see you!
Senior Data Quality Engineer
10 个月Thanks for sharing