När du definierar en funktion inom JavaScript, kommer den med några fördefinierade egenskaper; en av dessa är den illusiva prototypen. I den här artikeln ska jag beskriva vad det är och varför ska du använda det i dina projekt.
Prototypegenskapen är ursprungligen ett tomt objekt och kan få medlemmar tillagt det - som du skulle något annat objekt.
var myObject = funktion (namn) this.name = name; returnera detta; ; console.log (typ av myObject.prototype); // objekt myObject.prototype.getName = function () returnera detta.namn; ;
I utgåvan ovan har vi skapat en funktion, men om vi ringer myObject ()
, det kommer helt enkelt att returnera fönster
objekt, eftersom det definierades inom det globala området. detta
kommer därför att returnera det globala objektet, eftersom det ännu inte har blivit instantiated (mer om detta senare).
console.log (myObject () === fönster); // Sann
Varje objekt inom JavaScript har en "hemlig" egenskap.
Innan vi fortsätter vill jag diskutera den "hemliga" länken som gör prototyparbetet så som det gör.
Varje objekt inom JavaScript har en "hemlig" egenskap som läggs till den när den är definierad eller instantierad, namngiven __proto__
; Så här får man tillgång till prototypkedjan. Det är dock inte en bra idé att komma åt __proto__
inom din ansökan, eftersom den inte är tillgänglig i alla webbläsare.
De __proto__
egendom bör inte förväxlas med ett objekts prototyp, eftersom de är två separata egenskaper; som sagt, de går hand i hand. Det är viktigt att göra denna skillnad, eftersom det kan vara ganska förvirrande först! Vad betyder detta exakt? Låt mig förklara. När vi skapade myObject
funktion definierade vi ett objekt av typen Fungera
.
console.log (typ av myObject); // funktion
För de omedvetna, Fungera
är ett fördefinierat objekt i JavaScript, och som ett resultat har dess egna egenskaper (t.ex.. längd
och argument
) och metoder (t.ex.. ring upp
och tillämpa
). Och ja, det har också sitt eget prototypobjekt, liksom hemligheten __proto__
länk. Det betyder att någonstans i JavaScript-motorn finns en del kod som kan likna följande:
Function.prototype = arguments: null, längd: 0, call: function () // secret code, tillämpa: funktion () // hemlig kod ...
I sannhet skulle det förmodligen inte vara så så förenklat; Detta är bara för att illustrera hur prototypkedjan fungerar.
Så vi har definierat myObject
som en funktion och gav den ett argument, namn
; men vi ställer aldrig några egenskaper, till exempel längd
eller metoder, såsom ring upp
. Så varför fungerar följande?
console.log (myObject.length); // 1 (det är antalet tillgängliga argument)
Detta beror på när vi definierade myObject
, det skapade en __proto__
egendom och sätta sitt värde till Function.prototype
(illustrerad i koden ovan). Så när vi kommer åt myObject.length
, det letar efter en egendom av myObject
kallad längd
och finner inte en; Det reser sig sedan upp i kedjan, via __proto__ länk
, hittar fastigheten och returnerar den.
Du kanske undrar varför längd
är satt till 1
och inte 0
- eller något annat nummer för det faktumet. Det här är för att myObject
är faktiskt en förekomst av Fungera
.
console.log (myObject instanceof Function); // true console.log (myObject === Funktion); // false
När en förekomst av ett objekt skapas, __proto__
egenskapen är uppdaterad för att peka på konstruktörens prototyp, vilket i detta fall är Fungera
.
console.log (myObject .__ proto__ === Function.prototype) // true
Dessutom, när du skapar en ny Fungera
objekt, den inbyggda koden inuti Fungera
konstruktören räknar antalet argumenter och uppdateringar this.length
följaktligen, vilket i detta fall är 1
.
Om vi emellertid skapar en ny instans av myObject
använda ny
nyckelord, __proto__
kommer att peka på myObject.prototype
som myObject
är konstruktören i vår nya instans.
var myInstance = ny myObject ("foo"); console.log (myInstance .__ proto__ === myObject.prototype); // Sann
Förutom att ha tillgång till de inhemska metoderna inom Fungera
.prototyp, t.ex. ring upp
och tillämpa
, vi har nu tillgång till myObject
s metod, hämta namn
.
console.log (myInstance.getName ()); // foo var mySecondInstance = ny myObject ("bar"); console.log (mySecondInstance.getName ()); // bar console.log (myInstance.getName ()); // foo
Som du kan tänka dig är det här ganska praktiskt, eftersom det kan användas för att ritning ett objekt och skapa så många instanser som behövs - vilket leder mig till nästa ämne!
Säg till exempel att vi utvecklar ett kanvasspel och behöver flera (möjligen hundratals) objekt på skärmen samtidigt. Varje objekt kräver egna egenskaper, till exempel x
och y
koordinater, bredd
,höjd
, och många andra.
Vi kan göra det enligt följande:
var GameObject1 = x: Math.floor ((Math.random () * myCanvasWidth) + 1), y: Math.floor ((Math.random () * myCanvasHeight) + 1), bredd: 10, höjd: 10, rita: funktion () 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), bredd: 10, höjd: 10, rita: funktion () myCanvasContext.fillRect (this.x, this.y, this.width, this.height); ...;
... gör detta 98 fler gånger ...
Vad detta kommer att göra är att skapa alla dessa objekt i minnet - allt med separata definitioner för metoder, till exempel dra
och vad andra metoder kan behövas. Detta är verkligen inte idealiskt, eftersom spelet kommer att uppblåsa webbläsarna tilldelade JavaScript-minne och få det att springa väldigt långsamt ... eller till och med sluta svara.
Även om detta förmodligen inte skulle hända med endast 100 objekt, kan det ändå fungera som en ganska bra prestation, eftersom det kommer att behöva leta upp ett hundra olika objekt snarare än bara singeln prototyp
objekt.
För att programmet ska köras snabbare (och följa bästa praxis) kan vi (åter) definiera egenskapen för prototypen för GameObject
; varje förekomst av GameObject
kommer då att referera till metoderna inom GameObject.prototype
som om de var deras egna metoder.
// definiera GameObject-konstruktörsfunktionen var GameObject = funktion (bredd, höjd) this.x = Math.floor ((Math.random () * myCanvasWidth) + 1); this.y = Math.floor ((Math.random () * myCanvasHeight) + 1); this.width = width; this.height = height; returnera detta; ; // (re) definiera GameObject prototypobjektet GameObject.prototype = x: 0, y: 0, bredd: 5, bredd: 5, rita: funktion () myCanvasContext.fillRect (this.x, this.y, detta . bredd, this.height); ;
Vi kan sedan inställa GameObject 100 gånger.
var x = 100, arrayOfGameObjects = []; gör arrayOfGameObjects.push (nytt GameObject (10, 10)); medan (x--);
Nu har vi en uppsättning av 100 GameObjects, som alla delar samma prototyp och definition av dra
metod som drastiskt sparar minne inom applikationen.
När vi kallar dra
metod, kommer det att referera till exakt samma funktion.
var GameLoop = funktion () för (gameObject in arrayOfGameObjects) gameObject.draw (); ;
Ett objekts prototyp är ett levande objekt, så att säga. Detta innebär helt enkelt att om vi, efter att vi skapat alla våra GameObject-instanser, bestämmer oss för att vi istället för att rita en rektangel vill rita en cirkel, kan vi uppdatera vår GameObject.prototype.draw
metod i enlighet med detta.
GameObject.prototype.draw = function () myCanvasContext.arc (this.x, this.y, this.width, 0, Math.PI * 2, true);
Och nu, alla tidigare instanser av GameObject
och eventuella framtida fall kommer att rita en cirkel.
Ja, det här är möjligt. Du kanske är bekant med JavaScript-bibliotek, till exempel Prototype, som utnyttjar denna metod.
Låt oss använda ett enkelt exempel:
String.prototype.trim = function () returnera this.replace (/ ^ \ s + | \ s + $ / g, ");;
Vi kan nu komma åt detta som en metod för vilken sträng som helst:
"Foo bar" .trim (); // "Foo bar"
Det finns dock en mindre nackdel med detta. Du kan till exempel använda detta i din ansökan; men ett år eller två på vägen kan en webbläsare genomföra en uppdaterad version av JavaScript som innehåller en infödd trimma
metod inom Sträng
s prototyp. Det betyder att din definition av trimma
kommer att åsidosätta den ursprungliga versionen! Usch! För att övervinna detta kan vi lägga till en enkel kontroll innan du definierar funktionen.
om (! String.prototype.trim) String.prototype.trim = function () returnera this.replace (/ ^ \ s + | \ s + $ / g, ");;
Nu, om den existerar, kommer den att använda den inbyggda versionen av trimma
metod.
Som en tumregel är det allmänt sett en bra metod för att undvika att utöka föremål. Men som vad som helst kan regler brytas om det behövs.
Förhoppningsvis har den här artikeln skingrat lite på ryggraden för JavaScript som är prototyp. Du borde nu vara på väg att skapa mer effektiva applikationer.
Om du har några frågor angående prototyp, låt mig veta i kommentarerna, och jag gör mitt bästa för att svara på dem.