Mindössze annyit kell tudnia, hogy megértse a JavaScript prototípusát

Legtöbbször a JavaScript prototípusa összezavarja azokat az embereket, akik most kezdték el megtanulni a JavaScriptet - különösen, ha C ++ vagy Java háttérrel rendelkeznek.

A JavaScript-ben az öröklés kicsit másképp működik, mint a C ++ vagy a Java. A JavaScript-öröklés szélesebb körben „prototípusos öröklés” néven ismert.

A dolgokat nehezebb megérteni, ha classa JavaScript-ben is találkozik . Az új classszintaxis hasonlóan néz ki, mint a C ++ vagy a Java, de a valóságban másként működik.

Ebben a cikkben megpróbáljuk megérteni a „prototípusos öröklődést” a JavaScript-ben. Megvizsgáljuk az új classalapú szintaxist is, és megpróbáljuk megérteni, mi is ez valójában. Tehát kezdjük.

Először a régi iskola JavaScript funkciójával és prototípusával kezdjük.

A prototípus szükségességének megértése

Ha valaha dolgozott JavaScript tömbökkel, objektumokkal vagy karakterláncokkal, akkor észrevette, hogy van néhány módszer, amelyek alapértelmezés szerint elérhetők.

Például:

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

Gondolkodott már azon, hogy ezek a módszerek honnan származnak? Ön még nem határozta meg ezeket a módszereket.

Meg tudja határozni a saját módszereit, mint ez? Mondhatnád, hogy így tudsz:

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

Ez működni fog, de csak ennek az ún arr. Tegyük fel, hogy van egy másik változónk, amelyet arr2akkor hívunk, arr2.test()és hibát fog dobni: „TypeError: arr2.test nem függvény”.

Tehát hogyan válnak elérhetővé ezek a módszerek a tömb / karakterlánc / objektum minden egyes példánya számára? Készíthet saját módszereket ugyanazzal a viselkedéssel? A válasz igen. Meg kell csinálni a megfelelő módon. Ebben segít a JavaScript prototípusa.

Először nézzük meg, honnan származnak ezek a funkciók. Vegye figyelembe az alábbi kódrészletet:

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

Létrehoztunk két tömböt kétféleképpen: arr1array literálok és arr2a Arraykonstruktor függvényt. Mindkettő egyenértékű egymással, néhány különbséggel, amelyek nem számítanak ennek a cikknek.

Most jön a konstruktor függvény Array- ez egy előre definiált konstruktor függvény a JavaScript-ben. Ha megnyitja a Chrome fejlesztői eszközöket, és elmegy a konzolhoz, beírja console.log(Array.prototype)és lenyomja enter, az alábbiakat láthatja:

Itt láthatja az összes módszert, amelyre kíváncsi voltunk. Tehát most eljutunk oda, ahonnan ezek a funkciók érkeznek. Nyugodtan próbálja ki a String.prototypeés Object.prototype.

Készítsük el saját egyszerű konstruktor függvényünket:

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

Meg tudja azonosítani a fenti kóddal egy alapvető problémát? A probléma az, hogy a fenti megközelítéssel pazaroljuk a memóriát. Ne feledje, hogy a módszer tellMyNameminden foo. Minden alkalommal, amikor létrehozunk egy példányt, fooa módszer tellMyNamevégül helyet foglal a rendszer memóriájában. Ha tellMyNameaz összes esetre ugyanaz, akkor jobb, ha egyetlen helyen tartja, és az összes példányunkat erre a helyre hivatkozik. Lássuk, hogyan kell ezt megtenni.

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

Ellenőrizzük a különbséget a fenti megközelítéssel és az előző megközelítéssel. A fenti megközelítéssel, ha console.dir()a példákat, akkor valami ilyesmit fog látni:

Ne feledje, hogy csak a példányok tulajdonságaként rendelkezünk myname. tellMyNamealatt van meghatározva __proto__. Erre __proto__valamikor ráérek. Ami a legfontosabb, vegye figyelembe, hogy tellMyNamemindkét eset összehasonlítása igaznak értékeli. A JavaScript összehasonlítása csak akkor értékeli igaznak, ha referenciáik megegyeznek. Ez azt bizonyítja, hogy tellMyNamenem igényel több memóriát több példány esetén.

Lássuk ugyanezt az előző megközelítéssel:

Vegye figyelembe, hogy ez az idő tellMyNamea példányok tulajdonságaként van meghatározva. Már nincs ez alatt __proto__. Ezenkívül vegye figyelembe, hogy ezúttal a függvények összehasonlítása hamis értéket eredményez. Ez azért van, mert két különböző memóriahelyen vannak, és referenciáik eltérőek.

Remélem, hogy mára megértette a szükségességét prototype.

Most nézzünk meg néhány további részletet a prototípusról.

Minden egyes JavaScript függvénynek lesz prototypeobjektumtípusú tulajdonsága. A saját tulajdonságait a prototype. Amikor a függvényt konstruktor függvényként fogja használni, annak minden példánya örökölni fogja az prototypeobjektum tulajdonságait .

Most térjünk el arra a __proto__tulajdonságra, amelyet fentebb láttál. Ez __proto__egyszerűen hivatkozás arra a prototípus objektumra, amelyet a példány örökölt. Bonyolultnak hangzik? Valójában nem is olyan bonyolult. Vizualizáljuk ezt egy példával.

Vegye figyelembe az alábbi kódot. Már tudjuk, hogy egy tömb létrehozása tömb literálokkal örökölni fogja a tulajdonságokat Array.prototype.

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

Amit mondtam fentebb, hogy „ Az __proto__egyszerűen egy utalás a prototípus objektum, amely a példány örökölte ”. Tehát arr.__proto__ugyanúgy kell lennie Array.prototype. Ellenőrizzük ezt.

Most nem szabad hozzáférnünk a prototípus objektumhoz __proto__. Az MDN szerint a használat __proto__erősen nem ajánlott, és nem biztos, hogy minden böngészőben támogatott. Ennek helyes módja:

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

A fenti kódrészlet utolsó sora ezt mutatja, __proto__és Object.getPrototypeOfugyanazt adja vissza.

Itt az ideje egy kis szünetnek. Fogjon egy kávét vagy bármi mást, és próbálja ki egyedül a fenti példákat. Ha készen áll, térjen vissza ehhez a cikkhez, majd folytatjuk.

Prototípus láncolás és öröklés

A fenti 2. ábrán észrevette, hogy __proto__az első __proto__objektumon belül van még egy ? Ha nem, akkor görgessen felfelé egy kicsit a 2. ábra felé. Vessen egy pillantást és térjen vissza ide. Most megbeszéljük, mi is ez valójában. Ezt prototípus láncolásnak nevezik.

A JavaScript-ben az öröklődést prototípus-láncolással érjük el.

Tekintsük ezt a példát: Mindannyian értjük a „jármű” kifejezést. Autóbuszt járműként lehetne hívni. Az autót járműnek lehetne nevezni. A motorkerékpár nevezhető járműnek. A buszoknak, az autóknak és a motoroknak van néhány közös tulajdonságuk, ezért hívják őket járműnek. Például egyik helyről a másikra költözhetnek. Kerekeik vannak. Szarvuk van stb.

A buszok, az autók és a motorok szintén különböző típusúak lehetnek, például Mercedes, BMW, Honda stb.

A fenti ábrán a Bus bizonyos tulajdonságokat a járműtől, a Mercedes Benz Bus pedig a buszoktól örököl. Hasonló a helyzet a Car és a MotorBike esetében.

Hozd létre ezt a kapcsolatot a JavaScript-ben.

Először tegyünk fel néhány pontot az egyszerűség kedvéért:

  1. Az összes busznak 6 kereke van
  2. A gyorsítási és a fékezési eljárások buszokon, autókon és motorkerékpárokon eltérőek, de azonosak minden buszon, minden autóban és minden motorkerékpárban.
  3. Minden jármű kürtöt tud fújni.
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');

Engedje meg, hogy elmagyarázzam a fenti kódrészletet.

Van egy Vehiclekonstruktorunk, amely járműtípusra számít. Mivel minden jármű szarvát fújhatja, prototípusunkban van egy blowHorntulajdonság Vehicle.

Ahogy Busegy jármű, úgy az Vehicleobjektum tulajdonságait is örökölni fogja .

Feltételeztük, hogy az összes busznak 6 kereke lesz, és ugyanazok a gyorsítási és fékezési eljárások. Tehát megvan noOfWheels, acceleratorés a braketulajdonság a Busprototípusban van meghatározva .

Hasonló logika érvényes az autóra és a motorkerékpárra is.

Menjünk a Chrome Developer Tools -> Console oldalra, és hajtsuk végre a kódunkat.

A végrehajtás után, mi lesz 3 tárgy myBus, myCarés myMotorBike.

Írja console.dir(mybus)be a konzolt és nyomja meg enter. Használja a háromszög ikont a kibontásához, és valami hasonlót fog látni:

Alatt myBusvan tulajdonságunk makeés vehicleType. Vegye figyelembe, hogy __proto__a prototípus értéke Bus. Minden tulajdonságait prototípusa itt érhetők el: accelerator, brake, noOfWheels.

Most nézze meg, hogy az első __proto__objektum. Ennek az objektumnak egy másik __proto__objektuma van.

Amely alatt van blowHornés constructorvagyonunk.

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

Emlékszel a fenti sorra? Object.create(Vehicle.prototype)létrehoz egy üres objektumot, amelynek prototípusa Vehicle.prototype. Ezt az objektumot állítottuk be prototípusaként Bus. Ugyanis Vehicle.prototypenem adtunk meg egyetlen prototípust sem, így alapértelmezés szerint örököl Object.prototype.

Nézzük meg az alábbi varázslatot:

Hozzáférhetünk az makeingatlanhoz, mivel az a myBussaját tulajdonát képezi.

Az brakeingatlan myBusprototípusából férhetünk hozzá .

Az blowHorningatlanhoz a myBusprototípus prototípusából férhetünk hozzá .

Az hasOwnPropertyingatlanhoz a myBusprototípus prototípusának prototípusából férhetünk hozzá . :)

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

Itt az ideje egy újabb szünetnek. Ha készen áll, térjen vissza ehhez a cikkhez. Ezután folytatjuk, és ígérem, hogy ez az utolsó rész.

Az osztályok megértése a JavaScript-ben

Az MDN szerint:

Az ECMAScript 2015-ben bevezetett JavaScript osztályok elsősorban a JavaScript meglévő prototípus-alapú öröklődésének szintaktikai cukrát jelentik. Az osztály szintaxisa nem vezet be új objektum-orientált öröklési modellt a JavaScript-be.

A JavaScript-ben szereplő osztályok jobb szintaxist nyújtanak, hogy sokkal tisztább módon érhessük el a fentieket. Először vessünk egy pillantást az osztály szintaxisára.

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

constructorA módszer egy speciális típusú módszer. Ez automatikusan végrehajtásra kerül, amikor létrehoz egy ilyen osztályú példányt. Az osztály testében. Csak egy előfordulása constructorlehetséges.

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. :)