Prototyper i JavaScript

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.


Hva er prototype?

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

The Secret Link

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 myObjects 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!


Hvorfor bruker Prototype bedre?

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.


Hvordan bruke prototype

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 (); ;

Prototype er et Live Object

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.


Oppdaterer prototyper fra innfødte objekter

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


Konklusjon

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.