Når du definerer en funksjon innenfor JavaScript, kommer den med noen forhåndsdefinerte egenskaper; En av disse er den illusive prototypen. I denne artikkelen vil jeg detaljere hva det er, og hvorfor du bør bruke det i prosjektene dine.
Prototypegenskapen er i utgangspunktet en tom gjenstand, og kan ha medlemmer lagt til det - som du ville noe annet objekt.
var myObject = funksjon (navn) this.name = name; returnere dette; ; console.log (type av myObject.prototype); // objekt myObject.prototype.getName = function () return dette.navnet; ;
I koden ovenfor har vi opprettet en funksjon, men hvis vi ringer myObject ()
, det vil bare returnere vindu
objekt, fordi den ble definert innenfor det globale omfanget. dette
vil derfor returnere det globale objektet, som det ennå ikke har blitt opprettet (mer om dette senere).
console.log (myObject () === vindu); // true
Hvert objekt innenfor JavaScript har en "hemmelig" eiendom.
Før vi fortsetter, vil jeg diskutere den "hemmelige" lenken som gjør prototypen på den måten den gjør.
Hvert objekt innenfor JavaScript har en "hemmelig" eiendom lagt til den når den er definert eller instantiated, oppkalt __proto__
; Slik får du tilgang til prototypekjeden. Det er imidlertid ikke en god idé å få tilgang __proto__
i søknaden din, da den ikke er tilgjengelig i alle nettlesere.
De __proto__
Egenskapen bør ikke forveksles med prototypens objekt, da de er to separate egenskaper; Når det er sagt, går de hånd i hånd. Det er viktig å gjøre dette skillet, da det kan være ganske forvirrende først! Hva betyr dette nøyaktig? La meg forklare. Da vi opprettet myObject
funksjon, var vi definere et objekt av typen Funksjon
.
console.log (type av myObject); // funksjon
For de uvitende, Funksjon
er et forhåndsdefinert objekt i JavaScript, og som et resultat har dets egne egenskaper (f.eks. lengde
og argumenter
) og metoder (f.eks. anrop
og søke om
). Og ja, det har også sitt eget prototypeobjekt, så vel som hemmeligheten __proto__
link. Dette betyr at, et sted i JavaScript-motoren, er det en del kode som kan lignes på følgende:
Function.prototype = argumenter: null, lengde: 0, samtale: funksjon () // hemmelig kode, gjelder: funksjon () // hemmelig kode ...
I sannhet vil det nok ikke være ganske så forenklet; Dette er bare for å illustrere hvordan prototypekjeden fungerer.
Så vi har definert myObject
som en funksjon og gitt det ett argument, Navn
; men vi setter aldri noen egenskaper, for eksempel lengde
eller metoder, for eksempel anrop
. Så hvorfor gjør følgende arbeid?
console.log (myObject.length); // 1 (er mengden tilgjengelige argumenter)
Dette er fordi, da vi definerte myObject
, det opprettet en __proto__
eiendom og satt sin verdi til Function.prototype
(illustrert i koden ovenfor). Så når vi får tilgang myObject.length
, det ser etter en eiendom av myObject
kalt lengde
og finner ikke en; det reiser seg opp gjennom kjeden, via __proto__ link
, finner eiendommen og returnerer den.
Du lurer kanskje på hvorfor lengde
er satt til 1
og ikke 0
- eller noe annet nummer for det faktum. Dette er fordi myObject
er faktisk en forekomst av Funksjon
.
console.log (myObject instanceof Function); // true console.log (myObject === Funksjon); // falsk
Når en forekomst av et objekt er opprettet, vil __proto__
Eiendommen er oppdatert for å peke på konstruktørens prototype, som i dette tilfellet er Funksjon
.
console.log (myObject .__ proto__ === Function.prototype) // true
I tillegg, når du oppretter en ny Funksjon
objekt, den innfødte koden inne i Funksjon
Konstruktøren teller antall argumenter og oppdateringer this.length
Følgelig, som i dette tilfellet er 1
.
Hvis vi imidlertid oppretter en ny forekomst av myObject
bruker ny
søkeord, __proto__
vil peke på myObject.prototype
som myObject
er konstruktøren av vår nye forekomst.
var myInstance = ny myObject ("foo"); console.log (myInstance .__ proto__ === myObject.prototype); // true
I tillegg til å ha tilgang til de innfødte metodene innenfor Funksjon
.prototype, for eksempel anrop
og søke om
, Vi har nå tilgang til myObject
s metode, getName
.
console.log (myInstance.getName ()); // foo var mySecondInstance = ny myObject ("bar"); console.log (mySecondInstance.getName ()); // bar console.log (myInstance.getName ()); // foo
Som du kan forestille deg, er dette ganske nyttig, da det kan brukes til å tegne en gjenstand, og opprette så mange forekomster som nødvendig - som fører meg til neste emne!
Si for eksempel at vi utvikler et lerretspill og trenger flere (muligens hundrevis av) objekter på skjermen samtidig. Hvert objekt krever egne egenskaper, for eksempel x
og y
koordinater, bredde
,høyde
, og mange andre.
Vi kan gjøre det som følger:
var GameObject1 = x: Math.floor ((Math.random () * myCanvasWidth) + 1), y: Math.floor ((Math.random () * myCanvasHeight) + 1), bredde: 10, høyde: 10, tegne: funksjon () 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), bredde: 10, høyde: 10, tegne: funksjon () myCanvasContext.fillRect (this.x, this.y, this.width, this.height); ...;
... gjør dette 98 flere ganger ...
Hva dette vil gjøre er å opprette alle disse objektene i minnet - alt med separate definisjoner for metoder, for eksempel tegne
og hva andre metoder kan være påkrevd. Dette er absolutt ikke ideelt, da spillet vil oppblåse nettleserne tildelt JavaScript-minne, og få det til å løpe veldig sakte ... eller til og med slutte å svare.
Selv om dette formentlig ikke ville skje med bare 100 objekter, kan det fremdeles tjene til å være ganske et resultatstreff, da det må slå opp hundre forskjellige objekter, i stedet for bare singelen prototype
gjenstand.
For å gjøre programmet kjører raskere (og følg beste praksis), kan vi (re) definere prototypegenskapen til GameObject
; hver forekomst av GameObject
vil da referere til metodene innenfor GameObject.prototype
som om de var deres egne metoder.
// definer GameObject-konstruktørfunksjonen var GameObject = funksjon (bredde, høyde) this.x = Math.floor ((Math.random () * myCanvasWidth) + 1); this.y = Math.floor ((Math.random () * myCanvasHeight) + 1); this.width = width; this.height = height; returnere dette; ; // (re) definer GameObject prototype objekt GameObject.prototype = x: 0, y: 0, bredde: 5, bredde: 5, tegne: funksjon () myCanvasContext.fillRect (this.x, this.y, dette . bredde, dette høyde); ;
Vi kan da instantiere GameObject 100 ganger.
var x = 100, arrayOfGameObjects = []; gjør arrayOfGameObjects.push (nytt GameObject (10, 10)); mens (x--);
Nå har vi en rekke 100 GameObjects, som alle deler samme prototype og definisjon av tegne
metode som sparer minne i søknaden drastisk.
Når vi kaller tegne
metode, vil det referere til nøyaktig samme funksjon.
var GameLoop = funksjon () for (gameObject in arrayOfGameObjects) gameObject.draw (); ;
Et objekts prototype er et levende objekt, så å si. Dette betyr ganske enkelt at hvis vi, etter at vi har opprettet alle våre GameObject-forekomster, bestemmer oss for at vi i stedet for å tegne et rektangel vil tegne en sirkel, kan vi oppdatere vår GameObject.prototype.draw
metode tilsvarende.
GameObject.prototype.draw = function () myCanvasContext.arc (this.x, this.y, this.width, 0, Math.PI * 2, true);
Og nå, alle tidligere forekomster av GameObject
og eventuelle fremtidige tilfeller vil tegne en sirkel.
Ja, dette er mulig. Du kan være kjent med JavaScript-biblioteker, for eksempel Prototype, som utnytter denne metoden.
La oss bruke et enkelt eksempel:
String.prototype.trim = funksjon () return this.replace (/ ^ \ s + | \ s + $ / g, ");;
Vi kan nå få tilgang til dette som en metode for enhver streng:
"Foo bar" .trim (); // "foo bar"
Det er imidlertid en liten ulempe for dette. For eksempel kan du bruke dette i søknaden din; men et år eller to nedover veien, kan en nettleser implementere en oppdatert versjon av JavaScript som inneholder en innfødt trim
metode innenfor string
prototype. Dette betyr at din definisjon av trim
vil overstyre den opprinnelige versjonen! Yikes! For å overvinne dette, kan vi legge til en enkel sjekk før du definerer funksjonen.
hvis (! String.prototype.trim) String.prototype.trim = funksjon () returnere denne.replace (/ ^ \ s + | \ s + $ / g, ");;
Nå, hvis den eksisterer, vil den bruke den opprinnelige versjonen av trim
metode.
Som en tommelfingerregel anses det generelt som en god praksis for å unngå å utvide gjenstandsobjekter. Men som med noe, kan regler brytes, om nødvendig.
Forhåpentligvis har denne artikkelen kastet litt lys på ryggraden til JavaScript som er prototype. Du bør nå være på vei for å skape mer effektive applikasjoner.
Hvis du har spørsmål angående prototype, gi meg beskjed i kommentarene, og jeg vil gjøre mitt beste for å svare på dem.