En guide til prototypebaseret klassearv i JavaScript

Computersprog giver ofte en måde, hvorpå et objekt kan arves fra
et andet objekt. Det arvede objekt indeholder alle egenskaber fra dets overordnede objekt. Derudover vil det også specificere sit eget sæt unikke egenskaber.

Følg mig på Twitter for JavaScript-tip og bogmeddelelser.

JavaScript-objekter bruger prototypebaseret arv. Dens design er logisk ens (men forskellig i implementering) fra klassearv i strengt objektorienterede programmeringssprog.

Det kan løst beskrives ved at sige, at når metoder eller egenskaber er knyttet til objektets prototype, bliver de tilgængelige til brug på det objekt og dets efterkommere. Men denne proces finder ofte sted bag kulisserne.

Når du skriver kode, behøver du ikke engang at berøre prototypegenskab direkte. Når du udfører splitmetoden, vil du kalde den direkte fra en streng bogstavelig som: "hallo" .split ("e") eller fra en variabel: string.split (",");

Når du bruger klasse og udvider internt nøgleord, bruger JavaScript stadig prototypebaseret arv. Det forenkler bare syntaks. Måske er det derfor, det er vigtigt at forstå, hvordan prototypebaseret arv fungerer. Det er stadig kernen i sprogdesignet.

Dette er grunden til, at du i mange tutorials vil se String.prototype.split skrevet i stedet for bare String.split. Dette betyder, at der er en metodesplitning, der kan bruges med objekter af typen streng, fordi den er knyttet til objektets prototypeegenskab.

Oprettelse af et logisk hierarki af objekttyper

Kat og hund arves fra kæledyr, der er arvet fra dyr.

En hund og en kat deler lignende træk. I stedet for at oprette to forskellige klasser,
vi kan blot oprette en klasse Kæledyr og arve kat og hund derfra. Men selve kæledyrsklassen kan også arves fra klassen Dyr.

Før vi starter

At prøve at forstå prototyper er som at krydse floden, der går fra kodning til computersproget design. To helt forskellige videnområder.

Teknisk nok er kun den lette viden om klassen og udvider nøgleord nok til at skrive software. At prøve at forstå prototype er som at vove ind i de mørkere hjørner i sprogdesign. Og nogle gange kan det være indsigtsfuldt.

Denne tutorial alene vil ikke være nok. Jeg har kun fokuseret på nogle vigtige ting, der forhåbentlig leder dig i den rigtige retning.

Under kølerhjelmen

Ideen bag objektarv er at skabe struktur for et hierarki af
lignende objekter. Du kan også sige, at et barnobjekt er "afledt" fra dets forælder.

Sådan oprettes prototykæder i JavaScript.

Teknisk set er det sådan, det ser ud. Prøv ikke at tænke for meget på dette. Bare ved, at der helt øverst i hierarkiet er der Object-objektet. Derfor peger dens egen prototype på null. Der er intet andet over det.

Prototypebaseret objektarv

JavaScript understøtter objektarv via noget, der kaldes prototyper. Der er en objektegenskab kaldet prototype knyttet til hvert objekt.

At arbejde med klassen og udvide nøgleord er let, men det er ikke trivielt at forstå, hvordan prototypebaseret arv fungerer. Forhåbentlig løfter denne tutorial mindst noget af tågen!

Objektkonstruktørfunktioner

Funktioner kan bruges som objektkonstruktører. Navnet på en konstruktørfunktion starter normalt med et stort bogstav for at skelne mellem almindelige funktioner. Objektkonstruktører bruges til at oprette en forekomst af et objekt.

Nogle af de indbyggede JavaScript-objekter blev allerede oprettet efter de samme regler. F.eks. Arv, Array og String arves fra Object. Som vi diskuterede tidligere, betyder dette, at enhver ejendom, der er knyttet til Object, automatisk bliver tilgængelig på alle dets børn.

Constructors

Det er umuligt at forstå prototype uden at forstå anatomien for konstruktørfunktioner.

Så hvad sker der nøjagtigt, når vi opretter en brugerdefineret konstruktorfunktion? To egenskaber vises magisk i vores klassedefinition: konstruktør og prototype.konstruktør.

De peger ikke på det samme objekt. Lad os nedbryde dem:

Lad os sige, at vi definerer en ny klassekran (ved hjælp af enten funktion eller klassesøgleord).

En brugerdefineret konstruktør, vi netop har oprettet, er nu knyttet til prototypegenskabet i vores brugerdefinerede Crane-klasse. Det er et link, der peger på sin egen konstruktør. Det skaber cirkulær logik. Men det er kun et stykke af puslespillet.

Lad os nu se på Crane.constructor:

Crane.constructor peger selv på typen af ​​det objekt, det blev oprettet fra.
Da alle objektskonstruktører naturligt er funktioner, peger objektet Crane.constructor på er et objekt af typen Funktion, med andre ord funktionskonstruktøren.

Denne dynamik mellem Crane.prototype.constructor og Crane.constructor er det, der muliggør prototype-arv på molekylært niveau. Du er sjældent nødt til at tænke over dette, når du skriver JavaScript-kode. Men dette er bestemt et interviewspørgsmål.

Lad os kort gå over dette igen. Crane.prototype.constructor peger på sin egen konstruktør. Det er næsten som at sige "Jeg er mig."

Samme nøjagtige ting sker, når du definerer en klasse ved hjælp af klassesøgleord:

Men egenskaben Crane.constructor peger på funktionskonstruktør.

Og sådan er linket etableret.

Nu kan Crane-objektet i sig selv være prototypen på et andet objekt. Og det objekt kan være prototypen til et andet objekt. Og så videre. Kæden kan fortsætte for evigt.

Side note: I tilfælde af ES5-stil funktioner er selve funktionen den
konstruktør. Men ES6-nøgleordet i klassen placerer konstruktøren inden for sit omfang. Dette er bare en syntaktisk forskel.

Prototype-baseret arv

Vi bør altid bruge klassen og udvide nøgleord til at oprette og arve objekter. Men de er kun en slik indpakning til det, der faktisk foregår bag kulisserne.

Selvom oprettelse af objektarvhierarkier ved hjælp af syntaks i ES5-stil længe er forældet og sjældent ses blandt professionelle softwareudviklere, vil du ved at forstå det få en dybere indsigt i, hvordan det rent faktisk fungerer.

Lad os definere et nyt objekt Fugl og tilføje 3 egenskaber: type, farve og æg. Lad os også tilføje 3 metoder: flyve, gå og læg_egg. Noget alle fugle kan gøre:

Bemærk, at jeg med vilje har udformet lay_egg-metoden. Husk, hvordan vi
diskuterede tidligere, at Bird.prototype peger på sin egen konstruktør?

Du kunne alternativt have knyttet æglægningsmetoden direkte til Bird.prototype som vist i det næste eksempel:

Ved første øjekast kan det lyde, som om der ikke er nogen forskel mellem fastgørelsesmetoder ved hjælp af dette nøgleord i inde i Bird og blot tilføje det direkte til egenskaben Bird.prototype. Fordi det stadig fungerer rigtigt?

Men dette er ikke helt sandt. Jeg vil ikke gå nærmere ind på detaljerne lige nu, fordi jeg ikke fuldt ud forstår forskellen her. Men jeg planlægger at opdatere denne tutorial, når jeg samler lidt mere indsigt i emnet.

(kommentarer fra prototypeveteraner er velkomne!)

Ikke alle fugle er ens

Hele punktet med objektarv er at bruge en fælles klasse, der definerer alle egenskaber og metoder, som alle børn i den klasse automatisk arver. Dette gør koden kortere og gemmer hukommelse.

(Forestil dig at definere de samme egenskaber og metoder på alle børnobjekter individuelt igen. Det vil tage dobbelt så meget hukommelse.)

Lad os oprette flere forskellige typer fugle. Selvom alle af dem stadig kan flyve, gå og lægge (fordi de er arvet fra hovedfuglklassen), tilføjer hver unik fugletype sine egne metoder, der er unikke for denne klasse. For eksempel er det kun papegøjer, der kan tale. Og kun ravne kan løse gåder. Kun en sangfugl kan synge.

Papegøje
Lad os oprette en papegøje og arve den fra Bird:

Papegøje er en almindelig konstruktørfunktion ligesom Bird.

Forskellen er, at vi kalder Bird's konstruktør med Bird.call og videregiver papegøjen i denne sammenhæng, før vi lægger vores egne metoder fast. Bird.call tilføjer simpelthen alle dens egenskaber og metoder til Parrot. Derudover tilføjer vi også vores egen metode: tale.

Nu kan papegøjer flyve, gå, lægge æg og tale! Men vi var aldrig nødt til at definere fluevandring og lay_eggs-metoder inde i Parrot selv.

Ravn
Lad os skabe Raven på samme måde og arve den fra Bird:

Ravens er unikke, idet de kan løse gåder.

Songbird
Lad os nu oprette Songbird og arve den fra Bird:

Sangfugle kan synge.

Testning af fuglene

Vi skabte lige en masse forskellige fugle med unikke evner. Lad os se hvad
de er i stand til! Indtil nu definerede vi kun klasser og etablerede deres
hierarkisk forhold.

For at arbejde med objekter er vi nødt til at øjeblikkelig gøre dem:

Lad os give en spurve øjeblikkelig brug af den originale Fuglekonstruktør:

Sparve kan flyve, gå og lægge æg, fordi den er arvet fra Bird, der definerer alle disse metoder.

Men en spurv kan ikke tale. Fordi det ikke er en papegøje.

Lad os oprette en parakeet fra Parrot-klassen:

Da papegøje er arvet fra Bird, får vi alle dens metoder. En parakeet har den unikke evne til at tale, men den kan ikke synge! Sing-metoden er kun tilgængelig på objekter af typen Songbird. Lad os arve starr fra Songbird-klassen:

Endelig, lad os oprette en ravn og løse nogle gåder:

Brug af klasse og udvider nøgleord

ES5-stil konstruktører kan være en smule besværlige.

Heldigvis har vi nu klasse og udvider nøgleord til at udføre nøjagtigt det samme, som vi lige gjorde i det foregående afsnit.

klasse erstatter funktion

udvider og super () erstatter Bird.call fra de foregående eksempler.

Bemærk, at vi skal bruge super (), der kalder konstruktøren af ​​forældreklassen.

Denne syntaks ser meget mere håndterbar ud!

Nu skal vi bare indstille objektet:

Oversigt

Klassearv hjælper med at etablere et hierarki af objekter.

Klasser er de grundlæggende byggesten i dit applikationsdesign og arkitektur. De gør arbejdet med kode lidt mere menneskeligt.

Naturligvis var Bird bare et eksempel. I et ægte scenarie kan det være alt, der er baseret på, hvilken type applikation du prøver at oprette.

Køretøjsklasse kan være forælder til Motorcykel, bil eller tank.

Fisk kan bruges til at arve haj, guldfisk, gedde og så videre.

Arv hjælper os med at skrive renere kode og genudnytte det overordnede objekt til at gemme hukommelse ved gentagelse af objektegenskaber og metodedefinitioner.