Alt hvad du behøver at vide for at forstå JavaScript's prototype

Det meste af tiden forvirrer JavaScript's prototype mennesker, der lige er begyndt at lære JavaScript - især hvis de har en C ++ - eller Java-baggrund.

I JavaScript fungerer arv lidt anderledes sammenlignet med C ++ eller Java. JavaScript-arv er mere kendt som “prototypisk arv”.

Ting bliver sværere at forstå, når du også støder på classi JavaScript. Den nye classsyntaks ligner C ++ eller Java, men i virkeligheden fungerer den anderledes.

I denne artikel vil vi forsøge at forstå “prototype arv” i JavaScript. Vi ser også på den nye classbaserede syntaks og prøver at forstå, hvad det faktisk er. Så lad os komme i gang.

Først starter vi med den gamle skole JavaScript-funktion og prototype.

Forståelse af behovet for prototype

Hvis du nogensinde har arbejdet med JavaScript-arrays eller objekter eller strenge, har du bemærket, at der er et par metoder, der er tilgængelige som standard.

For eksempel:

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

Har du nogensinde spekuleret på, hvor disse metoder kommer fra? Du har ikke defineret disse metoder alene.

Kan du definere dine egne metoder som denne? Du kan sige, at du kan på denne måde:

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

Dette fungerer, men kun for denne variabel kaldet arr. Lad os sige, at vi har en anden variabel kaldet, arr2arr2.test()kaster en fejl "TypeError: arr2.test er ikke en funktion".

Så hvordan bliver disse metoder tilgængelige for hver forekomst af array / streng / objekt? Kan du oprette dine egne metoder med samme adfærd? Svaret er ja. Du skal gøre det på den rigtige måde. For at hjælpe med dette kommer JavaScript's prototype.

Lad os først se, hvor disse funktioner kommer fra. Overvej kodestykket nedenfor:

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

Vi har oprettet to arrays på to forskellige måder: arr1med array-bogstaver og arr2med Arraykonstruktorfunktion. Begge svarer til hinanden med nogle forskelle, der ikke betyder noget for denne artikel.

Kommer nu til konstruktorfunktionen Array- det er en foruddefineret konstruktorfunktion i JavaScript. Hvis du åbner Chrome Developer-værktøjer og går til konsollen og skriver console.log(Array.prototype)og rammer enter, vil du se noget som nedenfor:

Der vil du se alle de metoder, vi undrede os over. Så nu kommer vi fra, hvor disse funktioner kommer. Du er velkommen til at prøve med String.prototypeog Object.prototype.

Lad os oprette vores egen enkle konstruktorfunktion:

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

Kan du identificere et grundlæggende problem med ovenstående kode? Problemet er, at vi spilder hukommelse med ovenstående tilgang. Bemærk, at metoden tellMyNameer den samme for hver forekomst af foo. Hver gang vi opretter en forekomst af foometoden, tellMyNameender det med at tage plads i systemets hukommelse. Hvis tellMyNameer det samme for alle tilfælde, er det bedre at holde det på et enkelt sted og få alle vores forekomster til at henvise fra det sted. Lad os se, hvordan man gør dette.

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

Lad os kontrollere forskellen med ovenstående tilgang og tidligere tilgang. Med ovenstående tilgang, hvis du console.dir()forekomsterne, vil du se noget som dette:

Bemærk, at vi kun har en egenskab af de tilfælde myname. tellMyNameer defineret under __proto__. Jeg kommer til dette __proto__efter et stykke tid. Vigtigst bemærk, at sammenligning tellMyNameaf begge instanser vurderes til sandt. Funktionssammenligning i JavaScript vurderer kun sandt, hvis deres referencer er de samme. Dette beviser, at tellMyNameder ikke forbruges ekstra hukommelse i flere tilfælde.

Lad os se det samme med den tidligere tilgang:

Bemærk, at denne gang tellMyNameer defineret som en egenskab for forekomsterne. Det er ikke længere under det __proto__. Bemærk også, at denne gang at sammenligne funktionerne evalueres med falsk. Dette skyldes, at de er på to forskellige hukommelsessteder, og deres referencer er forskellige.

Jeg håber, at du nu forstår nødvendigheden af prototype.

Lad os nu se nærmere på prototypen.

Hver JavaScript-funktion har en prototypeegenskab, der er af objekttypen. Du kan definere dine egne egenskaber under prototype. Når du bruger funktionen som en konstruktorfunktion, arver alle forekomster af den egenskaber fra prototypeobjektet.

Lad os nu komme til den __proto__ejendom, du så ovenfor. Det __proto__er simpelthen en henvisning til det prototypeobjekt, som forekomsten har arvet fra. Lyder kompliceret? Det er faktisk ikke så kompliceret. Lad os visualisere dette med et eksempel.

Overvej koden nedenfor. Vi ved allerede, at oprettelse af en matrix med matrixbogstaver vil arve egenskaber fra Array.prototype.

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

Det, jeg lige har sagt ovenfor, er " Det __proto__er simpelthen en henvisning til det prototype-objekt, som forekomsten har arvet fra ". Så arr.__proto__burde være det samme med Array.prototype. Lad os kontrollere dette.

Nu skal vi ikke få adgang til prototypeobjektet med __proto__. Ifølge MDN er brug __proto__meget modløs og understøttes muligvis ikke i alle browsere. Den rigtige måde at gøre dette på:

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

The last line of the above code snippet shows that __proto__ and Object.getPrototypeOf return the same thing.

Now it’s time for a break. Grab a coffee or whatever you like and try out the examples above on your own. Once you are ready, come back to this article and we will then continue.

Prototype chaining & Inheritance

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]].

Nu er det tid til endnu en pause. Når du er klar, kom tilbage til denne artikel. Vi fortsætter derefter, og jeg lover, at dette er den sidste del.

Forståelse af klasser i JavaScript

Ifølge MDN:

JavaScript-klasser, introduceret i ECMAScript 2015, er primært syntaktisk sukker i forhold til JavaScript's eksisterende prototype-baserede arv. Klassesyntaks introducerer ikke en ny objektorienteret arvemodel til JavaScript.

Klasser i JavaScript giver bedre syntaks for at opnå det, vi gjorde ovenfor, på en meget renere måde. Lad os først se på klassens syntaks.

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

constructormetode er en særlig type metode. Det udføres automatisk, hver gang du opretter en forekomst af denne klasse. Inde i din klassekrop. Kun en forekomst af constructorer mulig.

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.

I denne artikel har jeg lige for klassedelen demonstreret, hvordan du kan opnå prototypiske arveklasser. Der er mere at vide om JavaScript-klasser, men det er uden for denne artikels anvendelsesområde. Tjek dokumentationen til klasser på MDN. Eller jeg vil prøve at skrive en hel artikel om klasser på et eller andet tidspunkt.

Hvis denne artikel hjalp dig med at forstå prototyper, ville jeg sætte pris på, hvis du kunne bifalde lidt.

Hvis du vil have mig til at skrive om et andet emne, så lad mig det vide i svarene.

Du kan også oprette forbindelse til mig via LinkedIn.

Tak fordi du læste. :)