Tout ce que vous devez savoir pour comprendre le prototype de JavaScript

La plupart du temps, le prototype de JavaScript déroute les personnes qui viennent tout juste de commencer à apprendre JavaScript, surtout si elles sont issues du C ++ ou de Java.

En JavaScript, l'héritage fonctionne un peu différemment par rapport à C ++ ou Java. L'héritage JavaScript est plus connu sous le nom d '«héritage prototypique».

Les choses deviennent plus difficiles à comprendre lorsque vous rencontrez également classen JavaScript. La nouvelle classsyntaxe ressemble à C ++ ou Java, mais en réalité, elle fonctionne différemment.

Dans cet article, nous allons essayer de comprendre «l'héritage prototypique» en JavaScript. Nous examinons également la nouvelle classsyntaxe basée et essayons de comprendre ce qu'elle est réellement. Alors, commençons.

Tout d'abord, nous allons commencer par la fonction et le prototype JavaScript à l'ancienne.

Comprendre le besoin de prototype

Si vous avez déjà travaillé avec des tableaux, des objets ou des chaînes JavaScript, vous avez remarqué que plusieurs méthodes sont disponibles par défaut.

Par exemple:

var arr = [1,2,3,4];arr.reverse(); // returns [4,3,2,1]
var obj = {id: 1, value: "Some value"};obj.hasOwnProperty('id'); // returns true
var str = "Hello World";str.indexOf('W'); // returns 6

Vous êtes-vous déjà demandé d'où venaient ces méthodes? Vous n'avez pas défini ces méthodes vous-même.

Pouvez-vous définir vos propres méthodes comme ça? Vous pourriez dire que vous pouvez de cette manière:

var arr = [1,2,3,4];arr.test = function() { return 'Hi';}arr.test(); // will return 'Hi'

Cela fonctionnera, mais uniquement pour cette variable appelée arr. Supposons que nous ayons une autre variable appelée, arr2puis arr2.test()lancera une erreur «TypeError: arr2.test is not a function».

Alors, comment ces méthodes deviennent-elles disponibles pour chaque instance de tableau / chaîne / objet? Pouvez-vous créer vos propres méthodes avec le même comportement? La réponse est oui. Vous devez le faire de la bonne manière. Pour vous aider, voici le prototype de JavaScript.

Voyons d'abord d'où viennent ces fonctions. Considérez l'extrait de code ci-dessous:

var arr1 = [1,2,3,4];var arr2 = Array(1,2,3,4);

Nous avons créé deux tableaux de deux manières différentes: arr1avec des littéraux de tableau et arr2avec une Arrayfonction constructeur. Les deux sont équivalents l'un à l'autre avec quelques différences qui n'ont pas d'importance pour cet article.

Venons-en maintenant à la fonction constructeur Array- c'est une fonction constructeur prédéfinie en JavaScript. Si vous ouvrez les outils de développement Chrome et accédez à la console et tapez console.log(Array.prototype)et appuyez sur, entervous verrez quelque chose comme ci-dessous:

Vous y verrez toutes les méthodes sur lesquelles nous nous interrogions. Alors maintenant, nous arrivons d'où viennent ces fonctions. N'hésitez pas à essayer avec String.prototypeet Object.prototype.

Créons notre propre fonction constructeur simple:

var foo = function(name) { this.myName = name; this.tellMyName = function() { console.log(this.myName); }}
var fooObj1 = new foo('James');fooObj1.tellMyName(); // will print Jamesvar fooObj2 = new foo('Mike');fooObj2.tellMyName(); // will print Mike

Pouvez-vous identifier un problème fondamental avec le code ci-dessus? Le problème est que nous gaspillons de la mémoire avec l'approche ci-dessus. Notez que la méthode tellMyNameest la même pour chaque instance de foo. Chaque fois que nous créons une instance de foola méthode tellMyNamefinit par prendre de la place dans la mémoire du système. Si tellMyNamec'est la même chose pour toutes les instances, il est préférable de le conserver au même endroit et de faire référence à toutes nos instances à partir de cet endroit. Voyons comment faire cela.

var foo = function(name) { this.myName = name;}
foo.prototype.tellMyName = function() { console.log(this.myName);}
var fooObj1 = new foo('James');fooObj1.tellMyName(); // will print Jamesvar fooObj2 = new foo('Mike');fooObj2.tellMyName(); // will print Mike

Vérifions la différence avec l'approche ci-dessus et l'approche précédente. Avec l'approche ci-dessus, si vous console.dir()utilisez les instances, vous verrez quelque chose comme ceci:

Notez qu'en tant que propriété des instances, nous n'avons que myname. tellMyNameest défini sous __proto__. J'y reviendrai __proto__après un certain temps. Notez surtout que la comparaison tellMyNamedes deux instances donne la valeur true. La comparaison de fonctions dans JavaScript évalue true uniquement si leurs références sont identiques. Cela prouve que cela tellMyNamene consomme pas de mémoire supplémentaire pour plusieurs instances.

Voyons la même chose avec l'approche précédente:

Notez que cette heure tellMyNameest définie comme une propriété des instances. Ce n'est plus sous ça __proto__. Notez également que cette fois, la comparaison des fonctions donne la valeur false. En effet, ils se trouvent à deux emplacements de mémoire différents et leurs références sont différentes.

J'espère maintenant que vous comprenez la nécessité de prototype.

Voyons maintenant plus en détail le prototype.

Chaque fonction JavaScript aura une prototypepropriété qui est du type objet. Vous pouvez définir vos propres propriétés sous prototype. Lorsque vous utiliserez la fonction comme fonction de constructeur, toutes ses instances hériteront des propriétés de l' prototypeobjet.

Venons-en maintenant à cette __proto__propriété que vous avez vue ci-dessus. Le __proto__est simplement une référence à l'objet prototype dont l'instance a hérité. Cela semble compliqué? Ce n'est en fait pas si compliqué. Visualisons cela avec un exemple.

Considérez le code ci-dessous. Nous savons déjà que la création d'un tableau avec des littéraux de tableau héritera des propriétés de Array.prototype.

var arr = [1, 2, 3, 4];

Ce que je viens de dire ci-dessus est « Le __proto__est simplement une référence à l'objet prototype dont l'instance a hérité ». Donc arr.__proto__devrait être la même chose avec Array.prototype. Vérifions cela.

Maintenant, nous ne devrions pas accéder à l'objet prototype avec __proto__. Selon MDN, l'utilisation __proto__est fortement déconseillée et peut ne pas être prise en charge par tous les navigateurs. La bonne façon de procéder:

var arr = [1, 2, 3, 4];var prototypeOfArr = Object.getPrototypeOf(arr);prototypeOfArr === Array.prototype;prototypeOfArr === arr.__proto__;

La dernière ligne de l'extrait de code ci-dessus montre cela __proto__et Object.getPrototypeOfrenvoie la même chose.

Il est maintenant temps de faire une pause. Prenez un café ou ce que vous voulez et essayez vous-même les exemples ci-dessus. Une fois que vous êtes prêt, revenez sur cet article et nous continuerons ensuite.

Chaînage de prototypes et héritage

In Fig: 2 above, did you notice that there is another __proto__ inside the first __proto__ object? If not then scroll up a bit to Fig: 2. Have a look and come back here. We will now discuss what that is actually. That is known as prototype chaining.

In JavaScript, we achieve Inheritance with the help of prototype chaining.

Consider this example: We all understand the term “Vehicle”. A bus could be called as a vehicle. A car could be called a vehicle. A motorbike could be called a vehicle. Bus, car, and motorbike have some common properties that's why they are called vehicle. For example, they can move from one place to another. They have wheels. They have horns, etc.

Again bus, car, and motorbike can be of different types for example Mercedes, BMW, Honda, etc.

In the above illustration, Bus inherits some property from vehicle, and Mercedes Benz Bus inherits some property from bus. Similar is the case for Car and MotorBike.

Let's establish this relationship in JavaScript.

First, let's assume a few points for the sake of simplicity:

  1. All buses have 6 wheels
  2. Accelerating and Braking procedures are different across buses, cars, and motorbikes, but the same across all buses, all cars, and all motorbikes.
  3. All vehicles can blow the horn.
function Vehicle(vehicleType) { //Vehicle Constructor this.vehicleType = vehicleType;}
Vehicle.prototype.blowHorn = function () { console.log('Honk! Honk! Honk!'); // All Vehicle can blow Horn}
function Bus(make) { // Bus Constructor Vehicle.call(this, "Bus"); this.make = make}
Bus.prototype = Object.create(Vehicle.prototype); // Make Bus constructor inherit properties from Vehicle Prototype Object
Bus.prototype.noOfWheels = 6; // Let's assume all buses have 6 wheels
Bus.prototype.accelerator = function() { console.log('Accelerating Bus'); //Bus accelerator}
Bus.prototype.brake = function() { console.log('Braking Bus'); // Bus brake}
function Car(make) { Vehicle.call(this, "Car"); this.make = make;}
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.noOfWheels = 4;
Car.prototype.accelerator = function() { console.log('Accelerating Car');}
Car.prototype.brake = function() { console.log('Braking Car');}
function MotorBike(make) { Vehicle.call(this, "MotorBike"); this.make = make;}
MotorBike.prototype = Object.create(Vehicle.prototype);
MotorBike.prototype.noOfWheels = 2;
MotorBike.prototype.accelerator = function() { console.log('Accelerating MotorBike');}
MotorBike.prototype.brake = function() { console.log('Braking MotorBike');}
var myBus = new Bus('Mercedes');var myCar = new Car('BMW');var myMotorBike = new MotorBike('Honda');

Allow me to explain the above code snippet.

We have a Vehicle constructor which expects a vehicle type. As all vehicles can blow their horns, we have a blowHorn property in Vehicle's prototype.

As Bus is a vehicle it will inherit properties from Vehicle object.

We have assumed all buses will have 6 wheels and have the same accelerating and braking procedures. So we have noOfWheels, accelerator and brake property defined in Bus’s prototype.

Similar logic applies for Car and MotorBike.

Let’s go to Chrome Developer Tools -> Console and execute our code.

After execution, we will have 3 objects myBus, myCar, and myMotorBike.

Type console.dir(mybus) in the console and hit enter. Use the triangle icon to expand it and you will see something like below:

Under myBus we have properties make and vehicleType. Notice the value of __proto__ is prototype of Bus. All the properties of its prototype are available here: accelerator, brake, noOfWheels.

Now have a look that the first __proto__ object. This object has another __proto__ object as its property.

Under which we have blowHorn and constructor property.

Bus.prototype = Object.create(Vehicle.prototype);

Remember the line above? Object.create(Vehicle.prototype) will create an empty object whose prototype is Vehicle.prototype. We set this object as a prototype of Bus. For Vehicle.prototype we haven’t specified any prototype so by default it inherits from Object.prototype.

Let’s see the magic below:

We can access the make property as it is myBus's own property.

We can access the brake property from myBus's prototype.

We can access the blowHorn property from myBus's prototype’s prototype.

We can access the hasOwnProperty property from myBus's prototype’s prototype’s prototype. :)

This is called prototype chaining. Whenever you access a property of an object in JavaScript, it first checks if the property is available inside the object. If not it checks its prototype object. If it is there then good, you get the value of the property. Otherwise, it will check if the property exists in the prototype’s prototype, if not then again in the prototype’s prototype’s prototype and so on.

So how long it will check in this manner? It will stop if the property is found at any point or if the value of __proto__ at any point is null or undefined. Then it will throw an error to notify you that it was unable to find the property you were looking for.

This is how inheritance works in JavaScript with the help of prototype chaining.

Feel free to try the above example with myCar and myMotorBike.

As we know, in JavaScript everything is an object. You will find that for every instance, the prototype chain ends with Object.prototype.

The exception for the above rule is if you create an object with Object.create(null)

var obj = Object.create(null)

With the above code obj will be an empty object without any prototype.

For more information on Object.create check out the documentation on MDN.

Can you change the prototype object of an existing object? Yes, with Object.setPrototypeOf() you can. Check out the documentation in MDN.

Want to check if a property is the object’s own property? You already know how to do this.Object.hasOwnProperty will tell you if the property is coming from the object itself or from its prototype chain. Check out its documentation on MDN.

Note that __proto__ also referred to as [[Prototype]].

Il est maintenant temps pour une autre pause. Une fois que vous êtes prêt, revenez sur cet article. Nous continuerons ensuite et je vous promets que c'est la dernière partie.

Comprendre les classes en JavaScript

Selon MDN:

Les classes JavaScript, introduites dans ECMAScript 2015, sont principalement du sucre syntaxique par rapport à l'héritage basé sur prototype existant de JavaScript. La syntaxe de classe n'introduit pas de nouveau modèle d'héritage orienté objet dans JavaScript.

Les classes en JavaScript fourniront une meilleure syntaxe pour réaliser ce que nous avons fait ci-dessus d'une manière beaucoup plus propre. Jetons d'abord un coup d'œil à la syntaxe de la classe.

class Myclass { constructor(name) { this.name = name; } tellMyName() { console.log(this.name) }}
const myObj = new Myclass("John");

constructormethod est un type particulier de méthode. Il sera automatiquement exécuté chaque fois que vous créez une instance de cette classe. À l'intérieur de votre corps de classe. Une seule occurrence de constructorest possible.

The methods that you will define inside the class body will be moved to the prototype object.

If you want some property inside the instance you can define it in the constructor, as we did with this.name = name.

Let’s have a look into our myObj.

Note that we have the name property inside the instance that is myObj and the method tellMyName is in the prototype.

Consider the code snippet below:

class Myclass { constructor(firstName) { this.name = firstName; } tellMyName() { console.log(this.name) } lastName = "lewis";}
const myObj = new Myclass("John");

Let’s see the output:

See that lastName is moved into the instance instead of prototype. Only methods you that you declare inside the Class body will be moved to prototype. There is an exception though.

Consider the code snippet below:

class Myclass { constructor(firstName) { this.name = firstName; } tellMyName = () => { console.log(this.name) } lastName = "lewis";}
const myObj = new Myclass("John");

Output:

Note that tellMyName is now an arrow function, and it has been moved to the instance instead of prototype. So remember that arrow functions will always be moved to the instance, so use them carefully.

Let’s look into static class properties:

class Myclass { static welcome() { console.log("Hello World"); }}
Myclass.welcome();const myObj = new Myclass();myObj.welcome();

Output:

Static properties are something that you can access without creating an instance of the class. On the other hand, the instance will not have access to the static properties of a class.

So is static property a new concept that is available only with the class and not in the old school JavaScript? No, it’s there in old school JavaScript also. The old school method of achieving static property is:

function Myclass() {}Myclass.welcome = function() { console.log("Hello World");}

Now let’s have a look at how we can achieve inheritance with classes.

class Vehicle { constructor(type) { this.vehicleType= type; } blowHorn() { console.log("Honk! Honk! Honk!"); }}
class Bus extends Vehicle { constructor(make) { super("Bus"); this.make = make; } accelerator() { console.log('Accelerating Bus'); } brake() { console.log('Braking Bus'); }}
Bus.prototype.noOfWheels = 6;
const myBus = new Bus("Mercedes");

We inherit other classes using the extends keyword.

super() will simply execute the parent class’s constructor. If you are inheriting from other classes and you use the constructor in your child class, then you have to call super() inside the constructor of your child class otherwise it will throw an error.

We already know that if we define any property other than a normal function in the class body it will be moved to the instance instead of prototype. So we define noOfWheel on Bus.prototype.

Inside your class body if you want to execute parent class’s method you can do that using super.parentClassMethod().

Output:

The above output looks similar to our previous function based approach in Fig: 7.

Wrapping up

So should you use new class syntax or old constructor based syntax? I guess there is no definite answer to this question. It depends on your use case.

In this article, for the classes part I have just demonstrated how you can achieve prototypical inheritance classes. There is more to know about JavaScript classes, but that’s out of the scope of this article. Check out the documentation of classes on MDN. Or I will try to write an entire article on classes at some time.

If this article helped you in understanding prototypes, I would appreciate if you could applaud a little.

If you want me to write on some other topic, let me know in the responses.

You can also connect with me over LinkedIn.

Thank You for Reading. :)