Prototypen in JavaScript

Wanneer u een functie definieert in JavaScript, heeft deze een paar vooraf gedefinieerde eigenschappen; een daarvan is het illusoire prototype. In dit artikel zal ik gedetailleerd beschrijven wat het is en waarom u het in uw projecten zou moeten gebruiken.


Wat is Prototype?

De prototype-eigenschap is in eerste instantie een leeg object en er kunnen leden aan worden toegevoegd - net als elk ander object.

var myObject = function (name) this.name = name; geef dit terug; ; console.log (typeof myObject.prototype); // object myObject.prototype.getName = function () return this.name; ;

In het bovenstaande fragment hebben we een functie gemaakt, maar als we bellen myObject (), het zal gewoon het venster object, omdat het is gedefinieerd binnen de globale scope. deze zal daarom het globale object retourneren, omdat het nog niet is geïnstantieerd (hierover later meer).

console.log (mijnObject () === venster); // waar

De geheime link

Elk object in JavaScript heeft een "geheime" eigenschap.

Voordat we verder gaan, wil ik graag de 'geheime' link bespreken die prototypen mogelijk maakt zoals het werkt.

Elk object in JavaScript heeft een "geheime" eigenschap toegevoegd wanneer het is gedefinieerd of geïnstantieerd, genaamd __proto__; dit is hoe de prototypeketen wordt benaderd. Het is echter geen goed idee om toegang te krijgen __proto__ in uw toepassing, omdat deze niet in alle browsers beschikbaar is.

De __proto__ eigenschap moet niet worden verward met het prototype van een object, omdat het twee afzonderlijke eigenschappen zijn; dat gezegd hebbende, ze gaan hand in hand. Het is belangrijk om dit onderscheid te maken, omdat het in het begin nogal verwarrend kan zijn! Wat betekent dit precies? Laat het me uitleggen. Toen we de myObject functie, definieerden we een object van het type Functie.

console.log (typeof myObject); // functie

Voor degenen die zich niet bewust zijn, Functie is een voorgedefinieerd object in JavaScript en heeft als gevolg daarvan zijn eigen eigenschappen (bijv. lengte en argumenten) en werkwijzen (bijv. telefoontje en van toepassing zijn). En ja, het heeft ook zijn eigen prototype-object, evenals het geheim __proto__ link. Dit betekent dat er ergens in de JavaScript-engine een beetje code is die op het volgende lijkt:

 Function.prototype = argumenten: null, length: 0, call: function () // geheime code, apply: function () // geheime code ...

In werkelijkheid zou het waarschijnlijk niet zo simplistisch zijn; dit is slechts om te illustreren hoe de prototypeketen werkt.

Dus we hebben gedefinieerd myObject als een functie en gaf het een argument, naam; maar we hebben nooit eigenschappen ingesteld, zoals lengte of methoden, zoals telefoontje. Dus waarom werkt het volgende??

console.log (myObject.length); // 1 (zijnde de hoeveelheid beschikbare argumenten)

Dit komt omdat, toen we het definieerden myObject, het creëerde een __proto__ eigenschap en stel de waarde ervan in op Function.prototype (geïllustreerd in de bovenstaande code). Dus wanneer we toegang krijgen myObject.length, het zoekt naar een eigendom van myObject riep lengte en vindt er geen; het reist dan de keten op, via de __proto__ link, vindt het eigendom en geeft het terug.

Je vraagt ​​je misschien af ​​waarom lengte ingesteld op 1 en niet 0 - of een ander nummer voor dat feit. Dit is zo omdat myObject is in feite een voorbeeld van Functie.

console.log (myObject instanceof Function); // true console.log (myObject === Function); // false

Wanneer een exemplaar van een object wordt gemaakt, wordt het __proto__ eigenschap wordt bijgewerkt naar het prototype van de constructor, wat in dit geval het geval is Functie.

console.log (myObject .__ proto__ === Function.prototype) // true

Bovendien wanneer u een nieuwe maakt Functie object, de native code in de Functie constructor telt het aantal argumenten en werkt het bij this.length dienovereenkomstig, wat in dit geval is 1.

Als we echter een nieuw exemplaar maken van myObject de ... gebruiken nieuwe trefwoord, __proto__ zal wijzen myObject.prototype zoals myObject is de constructor van onze nieuwe instantie.

 var myInstance = nieuw myObject ("foo"); console.log (myInstance .__ proto__ === myObject.prototype); // waar

Naast toegang hebben tot de native methoden binnen de Functie.prototype, zoals telefoontje en van toepassing zijn, we hebben nu toegang tot myObjectde methode, getName.

 console.log (myInstance.getName ()); // foo var mySecondInstance = nieuw myObject ("bar"); console.log (mySecondInstance.getName ()); // bar console.log (myInstance.getName ()); // foo

Zoals je je kunt voorstellen, is dit behoorlijk handig, omdat het kan worden gebruikt om een ​​object blauw te maken en zo veel exemplaren als nodig kan maken - wat me naar het volgende onderwerp leidt!


Waarom is Prototype beter gebruiken??

Stel dat we bijvoorbeeld een canvasgame ontwikkelen en meerdere (mogelijk honderden) objecten tegelijkertijd op het scherm nodig hebben. Elk object heeft zijn eigen eigenschappen, zoals X en Y coördinaten, breedte,hoogte, en vele anderen.

We kunnen het als volgt doen:

 var GameObject1 = x: Math.floor ((Math.random () * myCanvasWidth) + 1), y: Math.floor ((Math.random () * myCanvasHeight) + 1), width: 10, height: 10, draw: function () myCanvasContext.fillRect (this.x, this.y, this.width, this.height);  ...; var GameObject2 = x: Math.floor ((Math.random () * myCanvasWidth) + 1), y: Math.floor ((Math.random () * myCanvasHeight) + 1), width: 10, height: 10, draw: function () myCanvasContext.fillRect (this.x, this.y, this.width, this.height);  ...;

... doe dit nog 98 keer ...

Wat dit zal doen, is om al deze objecten in het geheugen te maken - allemaal met aparte definities voor methoden, zoals trek en welke andere methoden dan ook vereist zijn. Dit is zeker niet ideaal, omdat het spel de browsers blokkeert met toegewezen JavaScript-geheugen, waardoor het erg langzaam werkt ... of zelfs niet meer reageert.

Hoewel dit waarschijnlijk niet zou gebeuren met slechts 100 objecten, kan het nog steeds een behoorlijke prestatiehit zijn, omdat het honderd verschillende objecten moet opzoeken in plaats van alleen de single prototype voorwerp.


Hoe prototype te gebruiken

Om de applicatie sneller te laten werken (en de best practices te volgen), kunnen we de prototype-eigenschap van de GameObject; elk exemplaar van GameObject zal dan verwijzen naar de methoden binnenin GameObject.prototype alsof het hun eigen methoden waren.

 // definieer de constructorfunctie GameObject var GameObject = functie (breedte, hoogte) this.x = Math.floor ((Math.random () * myCanvasWidth) + 1); this.y = Math.floor ((Math.random () * myCanvasHeight) + 1); this.width = width; deze hoogte = hoogte; geef dit terug; ; // (her) definieer het GameObject-prototypeobject GameObject.prototype = x: 0, y: 0, width: 5, width: 5, draw: function () myCanvasContext.fillRect (this.x, this.y, this . breedte, deze hoogte); ;

We kunnen het GameObject vervolgens 100 keer instantiëren.

 var x = 100, arrayOfGameObjects = []; do arrayOfGameObjects.push (nieuw GameObject (10, 10));  while (x--);

Nu hebben we een array van 100 GameObjects, die allemaal hetzelfde prototype en dezelfde definitie delen trek methode, die het geheugen binnen de applicatie drastisch bespaart.

Wanneer we de trek methode, zal deze verwijzen naar exact dezelfde functie.

 var GameLoop = function () for (gameObject in arrayOfGameObjects) gameObject.draw (); ;

Prototype is een levend object

Het prototype van een object is een levend object, om zo te zeggen. Dit betekent simpelweg dat, als we al onze GameObject-instanties hebben gemaakt, we besluiten dat we in plaats van een rechthoek te tekenen, een cirkel willen tekenen, kunnen we onze GameObject.prototype.draw methode dienovereenkomstig.

 GameObject.prototype.draw = function () myCanvasContext.arc (this.x, this.y, this.width, 0, Math.PI * 2, true); 

En nu, alle eerdere voorbeelden van GameObject en eventuele toekomstige instanties zullen een cirkel tekenen.


Native Objects Prototypes bijwerken

Ja, dit is mogelijk. U bent wellicht bekend met JavaScript-bibliotheken, zoals Prototype, die van deze methode profiteren.

Laten we een eenvoudig voorbeeld gebruiken:

 String.prototype.trim = function () retourneer this.replace (/ ^ \ s + | \ s + $ / g, ");;

We kunnen dit nu benaderen als een methode voor elke reeks:

"Foo bar" .trim (); // "foo bar"

Dit heeft echter een klein nadeel. U kunt dit bijvoorbeeld gebruiken in uw toepassing; maar een jaar of twee verderop, kan een browser een bijgewerkte versie van JavaScript implementeren die een native bevat trimmen methode binnen de Draadhet prototype. Dit betekent dat uw definitie van trimmen zal de oorspronkelijke versie overschrijven! Yikes! Om dit te verhelpen, kunnen we een eenvoudige controle toevoegen voordat de functie wordt gedefinieerd.

 if (! String.prototype.trim) String.prototype.trim = function () retourneer this.replace (/ ^ \ s + | \ s + $ / g, ");;

Nu, als het bestaat, zal het de native versie van de gebruiken trimmen methode.

Als vuistregel wordt dit over het algemeen als een goede methode beschouwd om te voorkomen dat native-objecten worden uitgebreid. Maar, zoals met alles, kunnen regels worden overtreden, indien nodig.


Conclusie

Hopelijk heeft dit artikel enig licht geworpen op de backbone van JavaScript dat een prototype is. U zou nu op weg moeten zijn om efficiëntere applicaties te maken.

Als u vragen heeft over een prototype, laat het me weten in de opmerkingen en ik zal mijn best doen om ze te beantwoorden.