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.
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
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 myObject
de 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!
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.
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 (); ;
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.
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 Draad
het 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.
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.