Objekter i JavaScript. Opprette JavaScript-objekter

JavaScript er designet på et enkelt objektbasert paradigme. Et objekt er en samling av egenskaper, og en egenskap er en assosiasjon mellom et navn (eller nøkkel) og en verdi. En egenskaps verdi kan være en funksjon, i så fall er egenskapen kjent som en metode. I tillegg til objekter som er forhåndsdefinert i nettleseren, kan du definere dine egne objekter. Dette kapittelet beskriver hvordan du bruker objekter, egenskaper, funksjoner , og metoder, og hvordan du lager dine egne objekter.

Oversikt over objekter

Objekter i JavaScript, akkurat som i mange andre programmeringsspråk, kan sammenlignes med objekter i det virkelige liv. Konseptet med objekter i JavaScript kan forstås med virkelige, håndgripelige objekter.

I JavaScript er et objekt en frittstående enhet, med egenskaper og type. Sammenlign det med for eksempel en kopp. En kopp er en gjenstand, med egenskaper. En kopp har en farge, et design, vekt, et materiale den er laget av osv. På samme måte kan JavaScript-objekter ha egenskaper som definerer egenskapene deres.

Objekter og egenskaper

Et JavaScript-objekt har egenskaper knyttet til seg. En egenskap til et objekt kan forklares som en variabel som er knyttet til objektet. Objektegenskaper er i utgangspunktet det samme som vanlige JavaScript-variabler, bortsett fra vedlegget til objekter. Egenskapene til et objekt definerer egenskapene til objektet. Du får tilgang til egenskapene til et objekt med en enkel punktnotasjon:

Objektnavn.eiendomsnavn

Som alle JavaScript-variabler skiller både objektnavnet (som kan være en normal variabel) og egenskapsnavnet. Du kan definere en egenskap ved å tilordne den en verdi. La oss for eksempel lage et objekt kalt myCar og gi det egenskaper som heter make , model , og year som følger:

Var myCar = nytt objekt(); myCar.make = "Ford"; myCar.model = "Mustang"; myCar.year = 1969; myCar.color; // udefinert

Egenskaper for JavaScript-objekter kan også åpnes eller angis ved hjelp av en parentesnotasjon (for mer detaljer, se egenskapstilbehør). Objekter kalles noen ganger assosiative arrays, siden hver egenskap er knyttet til en strengverdi som kan brukes for å få tilgang til den. Så, for eksempel, kan du få tilgang til egenskapene til myCar-objektet som følger:

MyCar["make"] = "Ford"; myCar["model"] = "Mustang"; minBil["år"] = 1969;

Et objektegenskapsnavn kan være en hvilken som helst gyldig JavaScript-streng, eller hva som helst som kan konverteres til en streng, inkludert den tomme strengen. Imidlertid kan ethvert egenskapsnavn som ikke er en gyldig JavaScript-identifikator (for eksempel et egenskapsnavn som har et mellomrom eller bindestrek, eller som starter med et tall) bare nås ved å bruke hakeparentesnotasjonen. Denne notasjonen er også veldig nyttig når egenskapsnavn skal bestemmes dynamisk (når egenskapsnavnet ikke bestemmes før kjøretid). Eksempler er som følger:

// fire variabler opprettes og tildeles på en gang, // atskilt med kommaer var myObj = new Object(), str = "myString", rand = Math.random(), obj = new Object(); myObj.type = "Prikksyntaks"; myObj["date created"] = "Streng med mellomrom"; myObj = "Strengverdi"; myObj = "Tilfeldig nummer"; myObj = "Objekt"; myObj[""] = "Selv en tom streng"; console.log(myObj);

Vær oppmerksom på at alle nøkler i hakeparentesnotasjonen konverteres til streng med mindre de er symboler, siden JavaScript-objektegenskapsnavn (nøkler) bare kan være strenger eller symboler (på et tidspunkt vil private navn også bli lagt til som klassefeltforslaget utvikler seg, men du vil ikke bruke dem med form). For eksempel, i koden ovenfor, når nøkkelen obj legges til i myObj , vil JavaScript kalle opp obj.toString()-metoden og bruke denne resultatstrengen som den nye nøkkel.

Du kan også få tilgang til egenskaper ved å bruke en strengverdi som er lagret i en variabel:

Var propertyName = "make"; myCar = "Ford"; eiendomsnavn = "modell"; myCar = "Mustang";

Bruke en konstruktørfunksjon

Alternativt kan du opprette et objekt med disse to trinnene:

  1. Definer objekttypen ved å skrive en konstruktørfunksjon. Det er en sterk konvensjon, med god grunn, for å bruke stor forbokstav.
  2. Opprett en forekomst av objektet med ny .

For å definere en objekttype, lag en funksjon for objekttypen som spesifiserer dens navn, egenskaper og metoder. Anta for eksempel at du vil lage en objekttype for biler. Du vil at denne typen objekter skal hete Bil , og du vil at den skal ha egenskaper for merke, modell og år. For å gjøre dette, skriver du følgende funksjon:

Funksjon Bil(merke, modell, år) ( dette.merke = merke; denne.modell = modell; dette.år = år; )

Legg merke til bruken av dette for å tilordne verdier til objektets egenskaper basert på verdiene som er sendt til funksjonen.

Nå kan du lage et objekt kalt mycar som følger:

Var mycar = new Car("Eagle", "Talon TSi", 1993);

Denne uttalelsen oppretter mycar og tildeler den de angitte verdiene for egenskapene. Da er verdien av mycar.make strengen "Eagle", mycar.year er heltall 1993, og så videre.

Du kan opprette et hvilket som helst antall bilobjekter ved å ringe til nye. For eksempel,

Var kenscar = ny bil("Nissan", "300ZX", 1992); var vpgscar = new Car("Mazda", "Miata", 1990);

Et objekt kan ha en egenskap som i seg selv er et annet objekt. Anta for eksempel at du definerer et objekt kalt person som følger:

Funksjon Person(navn, alder, kjønn) ( dette.navn = navn; dette.alder = alder; dette.kjønn = kjønn; )

og instansier deretter to nye personobjekter som følger:

Var rand = ny person("Rand McKinnon", 33, "M"); var ken = new Person("Ken Jones", 39, "M");

Deretter kan du omskrive definisjonen av bil for å inkludere en eiereiendom som tar et personobjekt, som følger:

Funksjon Bil(merke, modell, år, eier) ( dette.merke = merke; denne.modell = modell; dette.år = år; denne.eier = eier; )

For å instansiere de nye objektene bruker du følgende:

Var bil1 = ny bil("Eagle", "Talon TSi", 1993, rand); var bil2 = ny bil("Nissan", "300ZX", 1992, ken);

Legg merke til at i stedet for å sende en bokstavelig streng eller heltallsverdi når du oppretter de nye objektene, sender setningene ovenfor objektene rand og ken som argumenter for eierne. Så hvis du vil finne ut navnet på eieren av car2, kan du få tilgang til følgende eiendom:

Bil2.eiernavn

Merk at du alltid kan legge til en egenskap til et tidligere definert objekt. For eksempel uttalelsen

Car1.color = "svart";

legger til en egenskapsfarge til bil1, og tildeler den verdien "svart". Dette påvirker imidlertid ikke andre objekter. For å legge til den nye egenskapen til alle objekter av samme type, må du legge egenskapen til definisjonen av bilobjekttypen.

Ved å bruke Object.create-metoden

Se også

  • For å dykke dypere, les om detaljene i JavaScripts objektmodell.
  • For å lære om ECMAScript 2015-klasser (en ny måte å lage objekter på), les kapittelet om JavaScript-klasser.



Objekter er et av kjernebegrepene i JavaScript. Da jeg først begynte å studere dem, virket de ganske enkle for meg: bare par med nøkler og verdier, som beskrevet i teorien.

Først etter en tid begynte jeg å forstå at temaet var mye mer komplekst enn jeg trodde. Og så begynte jeg å studere informasjon fra forskjellige kilder. Noen av dem ga en god idé om motivet, men jeg klarte ikke å se hele bildet med en gang.

I dette innlegget prøvde jeg å dekke alle aspekter ved å jobbe med objekter i JS, uten å gå for dypt inn i spesifikke detaljer, men også uten å utelate viktige detaljer som vil hjelpe deg å forstå emnet og føle deg mer selvsikker når du studerer det videre.

Så la oss starte med det grunnleggende.

En gjenstand

Et objekt i JavaScript er ganske enkelt en samling egenskaper, som hver er et nøkkelverdi-par. Du får tilgang til tastene ved å bruke en prikk ( obj.a) eller parentesnotasjon ( obj["a"]).

Husk at parenteser skal brukes hvis nøkkelen er:

  • er ikke en gyldig JavaScript-identifikator (den har et mellomrom, en bindestrek, starter med et tall...)
  • er en variabel.
En av egenskapene som objekter i JS mottar når de opprettes, kalles Prototype, og dette er et veldig viktig konsept.

Prototype

Hvert objekt i JavaScript har en intern egenskap kalt Prototype. I de fleste nettlesere kan du referere til det med notasjonen __proto__.

Prototype er en måte å håndheve eiendomsarv i JavaScript. På denne måten kan du dele funksjonalitet uten å duplisere kode i minnet. Metoden fungerer ved å skape en forbindelse mellom to objekter.

Enkelt sagt, Prototype lager en peker fra ett objekt til et annet.

Prototype kjede

Hver gang JS ser etter en egenskap i et objekt og ikke finner den direkte på selve objektet, sjekker den for tilstedeværelsen av egenskapen i prototypeobjektet. Hvis det ikke er noen egenskap i den, vil JS fortsette å se i prototypen til det tilknyttede objektet. Dette vil fortsette til JS finner en passende eiendom eller når slutten av kjeden.

La oss se på et eksempel:

Var cons = funksjon () ( this.a = 1; this.b = 2; ) var obj = new cons(); cons.prototype.b = 3; cons.prototype.c = 4;
ulemper er en konstruktør (ganske enkelt en funksjon som kan kalles ved hjelp av operatoren ny).

På den femte linjen lager vi et nytt objekt - nytt eksemplar ulemper. Umiddelbart etter opprettelsen obj får også prototypeegenskapen.

Og nå legger vi til egenskaper ( "b", "c") objektprototype ulemper.
La oss vurdere obj:

obj.a // 1- alt er likt her, obj.a er fortsatt 1.
obj.c?- y obj ingen eiendom c! Men som tidligere nevnt vil JS nå se etter den i prototypen obj og vil returnere verdien 4.

La oss nå tenke på hva meningen er obj.b og hvordan det blir når vi fjerner obj.b?

Obj.b er lik 2. Vi tildelte eiendommen b, men vi gjorde det for en prototype ulemper, så når vi sjekker obj.b, da får vi fortsatt 2. Imidlertid umiddelbart etter fjerning obj.b JS vil ikke lenger kunne finne b y o bj, og vil derfor fortsette å søke i prototypen og returnere verdien 3.

Opprette et objekt

Objekt bokstavelig: la obj = (a: 1);
Vi laget et objekt med følgende prototypekjede: obj ---> Objekt.prototype ---> null
Som du kan gjette, objekt.prototype er prototypen til objektet, og også slutten av prototypekjeden.

Object.create():var newObj = Object.create(obj);
U nyObj det vil være følgende kjede av prototyper: newObj ---> obj ---> Objekt.prototype ---> null

Konstruktør. Som i eksemplet ovenfor, er konstruktøren ganske enkelt en JS-funksjon som lar oss dra nytte av operatøren nyå lage nye forekomster av det.

ES6 klasser:

Class rectangle ( constructor(height, width) ( this.height = height; this.width = width; ) getArea() ( return this.height * this.width; ) ) la square = new rektangel(2, 2);
Torget- konstruktørforekomst rektangel, og så kan vi ringe square.getArea() //4, kvadratisk.bredde, samt alle funksjoner som er arvet fra objekt.prototype.

Hvilken vei er bedre? Hvis du planlegger å opprette flere forekomster, kan du bruke ES6 eller designeren. Hvis du planlegger å lage objektet en gang, er det bedre å spesifisere en bokstavelig, siden dette er den enkleste måten.

Og nå som vi har lært om prototype og etter å ha blitt kjent med alle måtene å lage nye objekter på, kan vi gå videre til å diskutere en av de mest forvirrende aspektene knyttet til objekter.

Sammenlign og endre objekter

I JavaScript-objekter tilhører referansetypen

Når vi lager et objekt la obj = (a: 1);, variabel obj får objektets minneadresse, men ikke verdien! Det er ekstremt viktig å forstå denne forskjellen, ellers kan det oppstå feil. Når vi lager et annet objekt la nyObj = obj, skaper vi faktisk pekeren til et bestemt minneområde obj, og ikke et helt nytt objekt.

Dette betyr at ved å gjøre nyObj.a = 2, vi endrer oss faktisk obj så det obj.a blir lik 2!

Denne tilnærmingen fører lett til feil, og det er derfor mange selskaper jobber med uforanderlige objekter. I stedet for endring allerede opprettet av dette objektet du må igjen opprette et nytt objekt (en kopi av originalen) og gjøre endringer i det. Det er slik viktige biblioteker som Redux fungerer, og dette er et av hovedkonseptene generelt funksjonell programmering. Du kan lese mer.

Likestilling

Det følger også av ovenstående at to objekter aldri kan være like, selv om de har de samme egenskapene. Dette skyldes det faktum at JS faktisk sammenligner minneplasseringen til objekter, og to objekter er aldri på samme minneplassering.

// To distinkte objekter med samme egenskaper er ikke like var fruit = (navn: "eple"); var fruktbjørn = (navn: "eple"); frukt === fruktbjørn; // return false // her peker frukt og fruktbjørn til det samme objektet var fruit = (navn: "eple"); var fruktbjørn = frukt; frukt === fruktbjørn; // returner sant
Så du har sannsynligvis allerede lurt på hvordan du kan sammenligne objekter eller hvordan du utfører forskjellige manipulasjoner med objekter, gitt kravet om deres uforanderlighet.

La oss vurdere flere muligheter.

Endre et objekt

La oss si at det er klart at vi faktisk ikke bør endre objekter, så vi ønsker å lage en kopi av det tilsvarende objektet og endre dets egenskaper. Kommer til unnsetning Object.assign().

Var obj = (a: 1, b: 2); var newObj = Object.assign((), obj,(a:2)) // (a: 2, b: 2 )
Hvis vi ønsker å endre eiendomsverdien en gjenstand obj, du kan bruke objekt.tilordne for å lage en kopi obj og dens endringer.

I eksemplet kan du se at vi først lager et tomt objekt, deretter kopierer vi verdiene obj og gjøre våre endringer, og til slutt oppnå et nytt og klart til bruk objekt.

Vær oppmerksom på at denne metoden ikke vil fungere for dyp kopiering. Når vi snakker om dypkopiering, mener vi at vi må kopiere et objekt med en eller flere egenskaper.

Const obj = (a: 1, b: ( a: 1 ) ); // b egenskap er et objekt
Object.assign() kopierer egenskapene til et objekt, så hvis verdien til egenskapen er en peker til et objekt, er det bare pekeren som kopieres.

En dyp kopi krever en rekursiv operasjon. Her kan du skrive en funksjon eller bare bruke en metode _.cloneDeep fra Lodash-biblioteket.

Sammenligning av objekter

Det er en kul teknikk for å jobbe med objekter - strengkonvertering. I følgende eksempel konverterer vi begge objektene til strenger og sammenligner dem:

JSON.stringify(obj1) === JSON.stringify(obj2)
Denne tilnærmingen er fornuftig fordi vi ender opp med å sammenligne strenger som er en peker til en verditype. Den dårlige nyheten er at det ikke alltid fungerer, hovedsakelig fordi rekkefølgen på objektets egenskaper ikke er garantert.

Annen Bra valg- bruk metoden _.er lik fra Lodash, som utfører dyp objektsammenligning.

Og før vi avslutter, la oss gå gjennom noen vanlige spørsmål om gjenstander. Dette vil hjelpe deg å dykke dypere inn i emnet og bruke den ervervede kunnskapen i praksis.

Prøv å tenke gjennom løsningen selv før du leser svaret.

Hvordan finne ut lengden på en gjenstand?

For å få svaret må du gå gjennom alle egenskapene til objektet en etter en og telle dem. Det er flere måter å utføre en slik iterasjon på:
  • for i. Denne metoden dekker alle tellbare egenskaper til et objekt og dets prototypekjeder. Vi har sett prototypen (og forhåpentligvis lært materialet), så det burde være klart at applikasjonen for i vil ikke alltid være sant for å få egenskapene til et objekt.
  • Objekt.nøkler. Denne metoden returnerer en matrise med nøklene til alle egen(tilhører det angitte objektet) teller egenskaper. Denne tilnærmingen er bedre fordi vi kun jobber med egenskapene til objektet, uten å få tilgang til egenskapene prototype. Det er imidlertid situasjoner der du har tildelt et attributt tallrike noen eiendom er falsk, og objekt.nøkler ender opp med å hoppe over det, og du får et feil resultat. Dette skjer sjelden, men i slike tilfeller vil det komme godt med getOwnPropertyNames.
  • getOwnPropertyNames returnerer en matrise som inneholder alt egen objektnøkler (både tellbare og utellelige).
Også verdt å nevne:
  • Objekt.verdier itererer over sine egne telleegenskaper og returnerer en matrise med tilsvarende verdier.
  • Objekt.oppføringer itererer over sine egne telleegenskaper og returnerer en matrise med nøkler og deres verdier.
Jeg tror du la merke til at de fleste metodene ovenfor returnerer en matrise. Dette er en mulighet til å dra full nytte av JavaScripts array-teknikker.

En slik metode er array.length. Som et resultat kan vi ganske enkelt skrive

La objLength = Object.getOwnPropertyNames(obj).length;

Hvordan sjekke om et objekt er tomt?

  1. JSON.stringify(myObj) === “()”?. Her bruker vi igjen strengkonverteringsverktøyet for enkelt å sjekke om et objekt er tomt (ved å sammenligne strenger, ikke objekter).
  2. !Object.keys(myobj).length // true?.? Som jeg nevnte, kan det være veldig nyttig å konvertere objektnøkler til en matrise. Her bruker vi den praktiske eiendommen lengde, arvet fra Array.prototype, bruker den til å sjekke lengden på nøklene i arrayet. I JS 0 blir til usann, så å legge til ! vi gjør det til sant. Eventuelle andre tall vil bli konvertert til falske.

Endelig

Jeg håper du nå føler deg tryggere på å skape og jobbe med objekter. La oss oppsummere:
  • Husk at objekter er av en referansetype, noe som betyr at det anbefales å jobbe med dem uten å endre de originale objektene.
  • Bli venner med eiendommen prototype og en kjede av prototyper.
  • Bli kjent med verktøyene som hjelper deg å jobbe med objekter. Husk at du kan gjøre objekter om til strenger, få en rekke nøkler, eller ganske enkelt iterere over egenskapene deres ved å bruke settet med metoder vi har blitt introdusert for.
Jeg ønsker deg lykke til i studiet JavaScript-objekter.

JavaScript er et objektorientert språk. Med unntak av grunnleggende språkkonstruksjoner som løkker og relasjonsoperatorer, implementeres nesten alle funksjoner i JavaScript ved å bruke objekter på en eller annen måte.

Noen ganger brukes objekter eksplisitt til å utføre spesifikke oppgaver, som for eksempel å behandle (X)HTML og XML-baserte dokumenter objektmodell dokument. I andre tilfeller er rollen til objekter mindre åpenbar, for eksempel rollen til String-objektet når du arbeider med primitive strengdata.

Tidligere kapitler har gitt eksempler som tydelig viser nytten av innebygde objekter, men i dette kapittelet skal vi dykke direkte inn i JavaScript-objekter.

Objekter i JavaScript

Objekter i JavaScript kan deles inn i fire grupper.

  1. Brukerobjekter laget av programmereren og har en struktur og enhet som passer til en spesifikk programmeringsoppgave. Vi vil diskutere mulighetene for å lage og bruke slike objekter i dette kapittelet.
  2. Innebygde objekter leveres av selve JavaScript-språket. Disse inkluderer objekter som er assosiert med datatyper (streng, tall og boolsk), objekter som lar deg lage egendefinerte objekter og sammensatte typer (objekt og matrise), og objekter som gjør vanlige oppgaver enklere (som dato, matematikk og RegExp). Mulighetene til innebygde objekter er regulert av språkstandarden ECMA-262 og, i mindre grad, av nettleserprodusentens spesifikasjoner.
  3. Nettleserobjekter som ikke er en del av JavaScript-språket, men som støttes av de fleste nettlesere. Eksempler på nettleserobjekter er Window, et objekt der nettleservinduer administreres og samhandler med brukeren, og Navigator, et objekt som gir klientkonfigurasjonsinformasjon. Fordi de fleste aspekter av nettleserobjekter ikke styres av noen standarder, kan egenskapene og oppførselen deres variere sterkt avhengig av både nettleseren og versjonen. Objekter av denne typen vil bli diskutert videre.
  4. Dokumentobjekter er en del av Document Object Model (DOM), definert av W3C-konsortiet. Slike objekter gir programmereren et strukturert grensesnitt til (X)HTML- og XML-dokumenter. Det er disse objektene som gir JavaScript-evne manipulering av nestede stilark (CSS - Cascade Style Sheet) og forenkle implementeringen av dynamisk HTML (DHTML). Tilgang til dokumentobjekter gis av nettleseren gjennom dokumentegenskapen til Window-objektet (window. document). Vi snakker mer om DOM senere.

Prinsipper for arbeid med objekter

En gjenstand er en uordnet samling av data, inkludert primitive datatyper, funksjoner og til og med andre objekter. Fordelen med objekter er at de samler all data og logikk som er nødvendig for å utføre en spesifikk oppgave på ett sted. String-objektet lagrer tekstdata og tilbyr mange av funksjonene som trengs for å handle på det. Selv om tilstedeværelsen av objekter i et programmeringsspråk slett ikke er nødvendig (det er for eksempel ingen objekter i C-språket), forenkler de absolutt bruken av språket.

Opprette objekter

Et objekt lages ved hjelp av en konstruktør - en funksjon spesiell type, som forbereder et nytt objekt for bruk ved å initialisere minnet som er okkupert av objektet. I kapittel 4 så vi at objekter kan lages ved å bruke den nye operatoren på konstruktørene deres. Ved å bruke denne operasjonen får konstruktøren til å lage et nytt objekt, hvis natur bestemmes av konstruktøren som kalles. For eksempel lager String()-konstruktøren String-objekter, og Array()-konstruktøren lager Array-objekter. Dette er hvordan objekttyper navngis i JavaScript: etter navnet på konstruktøren som oppretter objektet.
Her er et enkelt eksempel på hvordan du lager et objekt:

var city = new String();

Denne uttalelsen skaper en ny Strengeobjekt og plasserer en lenke til den i byvariabelen. Her får ikke konstruktøren noen argumenter, så byvariabelen vil motta sin standardverdi - i dette tilfellet den tomme strengen. Du kan gjøre dette eksemplet mer interessant ved å sende et argument til konstruktøren som spesifiserer startverdien:

var city = new String("San Diego");

I denne artikkelen ønsker jeg å snakke så fullstendig og konsekvent som mulig om hva et objekt er i JavaScript, hva dets evner er, hvilke relasjoner som kan bygges mellom objekter og hvilke metoder for "native" arv som følger av dette, hvordan alt dette påvirker ytelse og hva generelt å gjøre med alt dette :)

Artikkelen vil IKKE ha et ord om: emulering av det tradisjonelle klasseobjekt-paradigmet, syntaktisk sukker, innpakninger og rammer.

Kompleksiteten til materialet vil øke fra begynnelsen til slutten av artikkelen, så for proffene kan de første delene virke kjedelige og banale, men da vil det være mye mer interessant :)

Objekter i JavaScript

Mange artikler inneholder uttrykket "I JavaScript er alt et objekt." Teknisk sett er dette ikke helt sant, men det gjør det riktige inntrykket på nybegynnere :)

Faktisk er mye i språk et objekt, og selv det som ikke er et objekt kan ha noen av sine evner.

Det er viktig å forstå at ordet "objekt" brukes her ikke i betydningen "et objekt av en eller annen klasse." Et objekt i JavaScript er først og fremst bare en samling egenskaper (hvis du foretrekker det, kan du kalle det en assosiativ matrise eller liste) som består av nøkkel-verdi-par. Dessuten kan nøkkelen bare være en streng (selv for matriseelementer), men verdien kan være en hvilken som helst type data som er oppført nedenfor.

Så i JavaScript er det 6 grunnleggende typer data er Udefinert (som indikerer fravær av en verdi), null, boolsk (boolsk), streng (streng), tall (tall) og objekt (objekt).
Dessuten er de 5 første primitiv datatyper, men Object er det ikke. I tillegg kan vi konvensjonelt vurdere at objekttypen har "undertyper": array (Array), funksjon (Function), regulært uttrykk (RegExp) og andre.
Dette er en noe forenklet beskrivelse, men i praksis er det som regel tilstrekkelig.

I tillegg er de primitive typene streng, tall og boolsk på visse måter relatert til de ikke-primitive "undertypene" av objekt: henholdsvis streng, tall og boolsk.
Dette betyr at strengen "Hello, world", for eksempel, kan opprettes enten som en primitiv verdi eller som et String-objekt.
Kort fortalt er dette gjort slik at programmereren kan bruke metoder og egenskaper når han arbeider med primitive verdier som om de var objekter. Du kan lese mer om dette i den tilsvarende delen av denne artikkelen.

Arbeid fra lenken

En referanse er et middel for å få tilgang til et objekt under forskjellige navn. Arbeid med eventuelle gjenstander utføres utelukkende ved referanse.
La oss demonstrere dette med et eksempel:
test= function () (alert("Hei!" )) //Opprett en funksjon (alert("Hei!")) (og en funksjon, som vi husker, er et fullverdig objekt) og gjør testvariabelen til en referanse til den
test_link=test; //test_link refererer nå også til funksjonen vår
test(); //Hallo!
test_link(); //Hallo!


Som vi kan se, gir både den første lenken og den andre samme resultat.
Vi må innse at vi ikke har noen funksjon som heter test, og at testvariabelen ikke er en slags "hoved"- eller "primær"-kobling, og "test_link" er en mindre.

Vår funksjon, som alle andre objekter, er ganske enkelt et område i minnet, og alle referanser til dette området er helt likeverdige. Dessuten kan det hende at objektet ikke har noen referanser i det hele tatt - i dette tilfellet kalles det anonymt, og kan bare brukes umiddelbart etter opprettelse (for eksempel sendt til en funksjon), ellers vil det være umulig å få tilgang til det og vil snart destrueres av søppelsamleren (søppelhenting), som er ansvarlig for å slette objekter uten referanser.

La oss se hvorfor det er så viktig å forstå dette:

test=(prop: "noe tekst") //Lag et objekt med prop-egenskapen
test_link=test; //Opprett en annen lenke til dette objektet

Alert(test.prop); //noe tekst

//Endre egenskapen til objektet
test_link.prop="nytekst" ;

Alert(test.prop); //ny tekst
alert(test_link.prop); //ny tekst
/*Man kan si at eiendommen har endret seg både her og der - men slik er det ikke.
Det er bare ett objekt. Så egenskapen endret seg en gang, og koblingene fortsetter bare å peke dit de peker. */

//Legg til en ny egenskap og fjern den gamle
test.new_prop="hei" ;
slett test.prop;

Alert(test_link.prop); //undefined - denne egenskapen eksisterer ikke lenger
alert(test_link.new_prop);

//Slett lenken
slette test;
alert(test.ny_prop);
/*På dette tidspunktet vil skriptet gi en feil, fordi test ikke lenger eksisterer, og test.new_prop eksisterer ikke enda mer */
alert(test_link.new_prop); //Hallo
/* men her er alt i orden, for vi har ikke slettet selve objektet, men kun en lenke til det. Nå blir objektet vårt pekt på en enkelt lenke, test_link */

//Opprett et nytt objekt
test=test_link; //Først oppretter du testkoblingen på nytt
test_link=(prop: "noe tekst" ) //Og her er det nye objektet

Alert(test_link.prop); //noe tekst
alert(test.prop); //udefinert
/* Å lage et nytt objekt bryter referanselenken, og test og test_link peker nå til forskjellige objekter.
Faktisk tilsvarer dette å slette test_linken og lage den på nytt, men å peke på et annet objekt */
alert(test.ny_prop); //hei - nå inneholder testen en lenke til vårt aller første objekt


* Denne kildekoden ble uthevet med Source Code Highlighter.

Denne oppførselen til objekter reiser ofte mange spørsmål for nybegynnere, så jeg håper denne teksten vil bringe litt klarhet. Hvis vi ønsker å lage en virkelig ny, uavhengig kopi av et objekt, og ikke en lenke, er den eneste måten å gjøre dette på å lage et nytt objekt og kopiere de nødvendige egenskapene dit.

Det er også verdt å merke seg at arbeid med objekter ved referanse, i tillegg til de morsomme effektene som er oppført ovenfor, også gir betydelige minnebesparelser, noe som er viktig når ett objekt er mye brukt på forskjellige steder i programmet.

Primitive verdier

Som jeg nevnte ovenfor, kan datatypene streng og tall være enten objekter eller primitive verdier.
obj= new String("hei" ); //Lag en streng som et objekt
simple="hei" ; //Lag en primitiv verdi

Alert(obj); //Hallo
varsling (enkel); //hei - så langt er alt forutsigbart

Alert(obj.length); //6 - et objekt av typen String har en length-egenskap som lagrer lengden på strengen
alert(enkel.lengde); //6
/* Selv om simple ikke er et objekt, kan vi få tilgang til det samme settet med egenskaper som et objekt av typen String. Det er ganske praktisk */

Obj.prop="tekst" ;
simple.prop="tekst" ;

Alert(obj.prop); //tekst - siden obj er et vanlig objekt, kan vi enkelt gi det en annen egenskap
alert(enkel.prop); //undefined - men enkel er ikke et objekt, og dette tallet vil ikke fungere for oss

* Denne kildekoden ble uthevet med Source Code Highlighter.


Det samme gjelder for både tall- og boolsk-typene (vel, bortsett fra at de ikke har en lengde-egenskap, men de har en rekke andre flotte egenskaper).
Å bruke strenger og tall som objekter har ingen praktisk fordel, fordi primitive verdier er mer praktiske å bruke, men beholder samtidig all nødvendig funksjonalitet. For å fullføre bildet er det imidlertid nødvendig å forstå denne mekanismen.

Ikke forveksle bruken av primitive verdier med bruken av bokstaver - for eksempel, enten vi lager en matrise som "test=new Array()" eller som "test=", vil resultatet fortsatt være det samme objektet. Vi vil ikke motta noen primitive verdier.

Opprette og bruke objekter

Så, i motsetning til språk som implementerer klasse-objekt-paradigmet, trenger vi ikke å lage en klasse først og deretter lage et objekt av klassen. Vi kan umiddelbart lage et objekt, som er det vi vil gjøre i følgende eksempel:
test=(
simple_property: "Hei" ,
objektegenskap: (
user_1: "Petya" ,
user_2: «Vasya»
},
function_property: funksjon (bruker) (
alert(this .simple_property + ", " + this .object_property);
}
}

Test.function_property("bruker_1" ); //Hei, Petya.

* Denne kildekoden ble uthevet med Source Code Highlighter.


Vi har et testobjekt som har 3 egenskaper, som jeg håper navnene på taler for seg selv. Det som interesserer oss mest ved det er funksjonen_egenskapen, som inneholder funksjonen. En slik funksjon kan kalles en objektmetode.

Vår funksjon bruker dette nøkkelordet to ganger, som er en peker (dvs. en referanse) til objektet som funksjonen kalles fra. Dermed this.simple_property=test.simple_property="Hei", og this.object_property=test.object_property="Peter".

Det er viktig å være tydelig på at dette alltid peker på objektet som funksjonen kalles fra, og ikke til objektet den tilhører. Selv om de i dette eksemplet er det samme objektet, er dette ikke alltid tilfelle.

test.function_property("bruker_1" ); //Hei, Petya.

Test2=nytt objekt(); //En annen form for å lage et nytt objekt, lik test2=()

Test.function_property.call(test2, "bruker_1" ); //feil
/* Anropsmetoden lar deg kalle en funksjon på vegne av et annet objekt. I dette tilfellet kaller vi function_property-metoden til testobjektet, og denne peker ikke lenger til testobjektet, men til test2-objektet. Og fordi den har ikke egenskapen object_property, så når du prøver å få this.object_property vil skriptet gi en feilmelding */

//la oss prøve å fikse situasjonen
test2.simple_property="God dag" ;
test2.object_property=test.object_property; //I dette tilfellet vil vi bruke å spesifisere objektet ved referanse for ikke å duplisere koden

Test.function_property.call(test2, "bruker_1" ); //God dag, Petya.


* Denne kildekoden ble uthevet med Source Code Highlighter.

Det skal også fremgå tydelig fra eksempelet at det ikke er noen klare trinn for å lage og bruke et objekt. Et objekt kan endres på hvilken som helst måte når som helst - før, etter og til og med under bruk. Dette er også en viktig forskjell fra "tradisjonell" OOP.

Konstruktør

I eksemplet ovenfor laget vi 2 objekter som hadde noen likheter. Begge hadde egenskapene simple_property og object_property. Åpenbart, når du skriver ekte kode, oppstår ofte oppgaven med å lage identiske eller ganske enkelt lignende objekter. Og selvfølgelig trenger vi ikke å lage hvert slikt objekt manuelt.

En designer vil hjelpe oss. En konstruktør i JavaScript er ikke en del av en klasse (fordi det ikke er noen klasser), men bare en funksjon i seg selv. Den vanligste funksjonen.

make_me= funksjon (_navn) (
alert("Jeg ble lansert" );
dette .navn=_navn;

}


/* La oss finne ut hva som skjer her. Tolken ser den nye operatøren og sjekker hva som står til høyre for den. Fordi make_me er en funksjon, og den kan brukes som en konstruktør, så opprettes et nytt objekt i minnet og make_me-funksjonen startes for kjøring, og dette peker nøyaktig til dette nye objektet. Deretter legges dette objektet til med en navneegenskap, som tildeles verdien fra _name-argumentet, og en vis_navn-metode. Også (jeg vet ikke på hvilket tidspunkt, men det spiller ingen rolle) underordnede variabelen begynner å peke på vårt splitter nye, nettopp fødte objekt */

Alert(barn.navn); //Vasya
child.show_name(); //Vasya


barn2.vis_navn(); //Peter

Barn2.vis_navn=funksjon () (varsel( "Jeg vil ikke si navnet mitt");} //Ikke glem at vi kan endre objektene våre når som helst
barn2.vis_navn(); //Jeg vil ikke si navnet mitt

Child.show_name(); //Vasya - barn påvirker ikke hverandre på noen måte


* Denne kildekoden ble uthevet med Source Code Highlighter.

Du kan også sammenligne en designer med en far - han føder et barn, og gir ham visse egenskaper, men umiddelbart etter opprettelsen blir barnet helt uavhengig av forelderen og kan bli veldig forskjellig fra brødrene hans.
Hvis vi husker beskrivelsen av datatyper i begynnelsen av artikkelen, blir det klart at Objekt og dets undertyper (Function, Array og andre) faktisk er konstruktører som gir det opprettede objektet egenskapene til en funksjon, array, etc.

Så dette er allerede mye bedre. Vi har nå muligheten til å lage objekter etter et eller annet mønster. Men alt er ikke bra ennå. For det første opptar hvert objekt vi lager og alle dets egenskaper og metoder en egen plass i minnet, selv om de på mange måter gjentas. For det andre, hva om vi ønsker å opprettholde forbindelsen mellom foreldre og barn, og kunne endre alle barneobjekter på en gang. En prototype vil hjelpe oss.

Prototype

Akkurat som hvert barn har en far og mor (i hvert fall i biologisk forstand), har alle objekter i JavaScript. Og hvis faren, som vi har bestemt, jobber som designer, så er moren nettopp prototypen. La oss se hvordan dette skjer:
make_me= funksjon (_navn) (
alert("Jeg ble lansert" );
dette .navn=_navn;
denne .vis_navn=funksjon () (varsling(dette .navn);)
}
/*
Når tolken ser funksjonsnøkkelordet, sjekker tolken koden til høyre for det, og så videre. alt er ok - det skaper et nytt objekt i minnet, som også er vår funksjon. Deretter opprettes automatisk (uten programmererintervensjon) en prototypeegenskap for denne funksjonen, som refererer til et tomt objekt. Hvis vi gjorde dette manuelt, ville det se ut som make_me.prototype=new Object();

Deretter blir dette objektet (pekt på av prototype-egenskapen) også automatisk gitt en konstruktør-egenskap, som peker tilbake til funksjonen. Det viser seg at dette er en syklisk kobling.

Nå er dette objektet, som kan beskrives som (konstruktør: ...her er en referanse til en funksjon...) prototypen til funksjonen.
*/

//Objekt - faktisk et objekt
alert(type av make_me.prototype.constructor); //Funksjon er vår funksjon
alert(make_me.prototype.constructor === make_me); //ekte

//Legg til en ny metode til make_me-funksjonsprototypen

Child=new make_me("Vasya"); //Jeg ble lansert
/* Nå, i tillegg til alt beskrevet i forrige eksempel, opprettes en ekstra skjult egenskap [] i underordnet objekt, som peker til det samme objektet som make_me.prototype. Fordi eiendommen er skjult, vi kan verken se verdien eller endre den - hvordan den spiller viktig rolle i fremtidig arbeid */

Alert(barn.navn); //Vasya
child.show_name(); //Vasya

Child.set_name("Kolya" );
/* Først ser tolken etter metoden set_name i underordnet objekt. Siden den ikke er der, fortsetter den å søke i barnet.[ eiendom, finner den der og kjører den. */
child.show_name(); //Kolya - nå heter Vasya Kolya :)

Make_me.prototype.show_name2=funksjon () (alert("Hei, " + dette .navnet;) //Fordi en prototype er en vanlig gjenstand, vi kan like godt endre den i farten

Child2=new make_me("Petya" );
barn2.vis_navn2(); //Hei, Petya
child.show_name2(); //Hei, Kolya - endringer i prototypen påvirker ikke bare nyopprettede objekter, men også alle gamle

Barn2.vis_navn2=funksjon () (varsel( "Jeg vil ikke si navnet mitt");} //Vi kan fortsatt endre selve objektet, og den nye metoden show_name2 i dette objektet (og bare i det) vil så å si "overskrive" den gamle metoden fra prototypen
barn2.vis_navn2(); //Jeg vil ikke si navnet mitt - fordi... vi har nå vår egen metode show_name2, så kalles den, og søket i prototypen forekommer ikke

Child.show_name2(); //Hei, Kolya - alt er fortsatt det samme her

Make_me.prototype=(prop: "hei") //La oss prøve å gjenskape prototypen igjen

Alert(child.prop); //udefinert
child.show_name2(); //Hei Kolya
/* Hvis du husker hva arbeid ved referanse er, så er alt klart. Å gjenskape prototypen bryter forbindelsen, og nå peker []-egenskapen til child- og child2-objektene til ett objekt (som tidligere var prototypen til make_me-funksjonen), og make_me.prototype-egenskapen peker til et annet objekt, som er det nye. prototype av make_me-funksjonen */

Child3=new make_me("Oleg" );
alert(barn3.prop); //hei - som forventet


* Denne kildekoden ble uthevet med Source Code Highlighter.

Som det fremgår av eksempelet, så lenge faren forblir trofast mot moren (det vil si så lenge funksjonstypen forblir den samme), er alle barn avhengige av moren og er følsomme for alle endringer i henne. Men så snart foreldrene skilles (designeren endrer prototypen til en annen), løper barna umiddelbart i alle retninger og det er ikke lenger kontakt med dem.

Litt om terminologi
Så lenge den primære forbindelsen mellom designeren og prototypen ikke er brutt, kan vi observere følgende bilde:

make_me= funksjon (_navn) (
alert("Jeg ble lansert" );
dette .navn=_navn;
denne .vis_navn=funksjon () (varsling(dette .navn);)
}

Make_me.prototype.set_name=funksjon (_navn) (dette .navn=_navn;)
child=new make_me("Vasya" );

Alert(type av make_me.prototype); //objekt - funksjonen har en prototype-egenskap
alert(type av barn.prototype); //undefined - det opprettede objektet har IKKE en prototype-egenskap
alert(child.constructor.prototype === make_me.prototype); //true - men objektet har en konstruktøregenskap, som peker på konstruktørfunksjonen make_me, som igjen har en prototypeegenskap


* Denne kildekoden ble uthevet med Source Code Highlighter.

Som jeg har lagt merke til etter å ha lest mange fora om dette emnet, er hovedproblemet folk har når de forveksler prototypeegenskapen til en funksjon med den skjulte []-egenskapen til objektet opprettet av den funksjonen.
Begge disse egenskapene er en referanse til det samme objektet (så lenge prototypens primære forbindelse til konstruktøren ikke er brutt), men de er likevel forskjellige egenskaper, med forskjellige navn, en av dem er tilgjengelig for programmereren, og den andre er ikke.

Det er alltid nødvendig å tydelig forstå at hvis vi snakker om prototypen til konstruktøren, så er dette alltid prototype-egenskapen, og hvis vi snakker om prototypen til det opprettede objektet, så er dette den skjulte egenskapen [].

Arv

Nå vet vi at hvert objekt har en skjult prototypereferanse, og hver prototype er et vanlig objekt.
De mest følsomme leserne har allerede fanget lukten av rekursjon :)
Faktisk fordi en prototype er et vanlig objekt, så har den på sin side en kobling til prototypen sin, og så videre. Slik implementeres prototypehierarkiet.
fugl = funksjon()() //Dette er fuglens konstruktør
bird.prototype.cry=function ()(alert("Cry!");) //Fuglen kan skrike
bird.prototype.fly=function ()(alert("Jeg flyr!");) //og fly

And=funksjon () ()
and.prototype=ny fugl();
duck.prototype.cry=function ()(alert("Quack-quack!" ;) //Duck skriker annerledes
duck.prototype.constructor=and; //Sett egenskapen prototype.constructor med makt til å dukke, fordi ellers vil det referere til fugl

Billy = ny and(); //Billy er anda vår
billy.fly(); //Jeg flyr! - Billy kan fly fordi han er en fugl.
billy.cry(); //Kvakk KVAKK! - Billy skriker kvakk-kvakk fordi han er en and.


* Denne kildekoden ble uthevet med Source Code Highlighter.

På denne måten kan du implementere et hierarki av ethvert hekkenivå.

Stjerneoppgave

Nå, siden vi vet så mye om alt dette, la oss prøve å finne ut hvor mye som skjer på disse tre linjene
make_me= funksjon()()
barn=ny make_me();
alert(child.toString()); //utganger

* Denne kildekoden ble uthevet med Source Code Highlighter.

På den første linjen lager vi ny funksjon og en make_me-variabel som peker til denne funksjonen. Dette lager en funksjonsprototype, make_me.prototype, som inneholder en konstruktøregenskap som peker til make_me.
Men det er ikke alt :)
Fordi make_me-funksjonen er også et objekt, da har den på sin side en far og en mor, dvs. designer og prototype. Konstruktøren er en innebygd funksjon av funksjonen()-språket, og prototypen er et objekt som inneholder metodene kall, anvende, etc. – Det er takket være denne prototypen at vi kan bruke disse metodene i enhver funksjon. Derfor har make_me-funksjonen en []-egenskap som peker til Function.prototype.

I sin tur er prototypen til en funksjonskonstruktør også et objekt, hvis konstruktør er (overraskelse!) Objekt (dvs. Function.prototype.[].constructor===Object), og prototypen er et objekt som inneholder standardegenskapene og metoder for objektet, slik som toString, hasOwnProperty og andre (med andre ord - Function.prototype.[]["hasOwnProperty"] - dette er nøyaktig metoden vi kan bruke i alle avledede objekter - og dette er den egen metoden av dette objektet, og ikke et arvet ). På denne interessante måten oppdager vi at alle slags objekter er avledet fra Objekt.

Kan vi fortsette videre? Det viser seg ikke. Object.prototype inneholder de grunnleggende egenskapene til objektet nettopp fordi det ikke har sin egen prototype. Object.prototype.[]=null; På dette tidspunktet stopper reisen gjennom prototypekjeden på jakt etter en eiendom eller metode.

En annen interessant fakta- Konstruktøren av objektet er funksjon. De. Objekt.[].constructor===Funksjon.
Det er en annen sirkulær referanse - konstruktøren av Object er Function, og konstruktøren av Function.prototype er Object.

La oss gå tilbake til vårt eksempel. Vi har allerede forstått hvordan funksjonen er opprettet, la oss nå gå videre til den andre linjen. Der lager vi et barneobjekt hvis konstruktør er make_me-funksjonen og hvis prototype er make_me.prototype.

Vel, i tredje linje ser vi hvordan tolken går oppover i kjeden, fra barn til barn.[] (aka make_me.prototype), så til barn.[].[] (aka Object.prototype), og finner allerede der toString-metoden, som starter utførelse.

Urenheter

Det kan virke som om arv gjennom prototyper er den eneste mulige måten i JavaScript. Dette er feil.
Vi har å gjøre med et veldig fleksibelt språk som gir muligheter fremfor regler.

For eksempel, hvis vi ønsker det, kan vi ikke bruke prototyper i det hele tatt, men programmere ved å bruke konseptet mixins. Til dette trenger vi våre gode gamle venner - designere.

//Dette er en menneskelig konstruktør
mann=funksjon() (
this .live=function ()(alert("Jeg er i live" ;) //Mennesket vet hvordan det skal leve
denne .walk=function ()(alert("Jeg går" ;) //Mannen kan gå
}

//Dette er dikterens konstruktør
poet=funksjon ()(
denne .kill=funksjonen ()(varsel( "Poeten drepte en mann");} //En poet kan drepe en person
denne .live=function ()(alert("Jeg er død" ;) //En person vil dø av dette
}

Vladimir=ny mann(); //Vladimir er en mann
vladimir.live(); //Jeg lever - han er i live
vladimir.walk(); //Jeg går - han går

Poet.call(vladimir); //Utfør dikterkonstruktøren for vladimir-objektet
vladimir.kill(); //Poeten drepte en mann
vladimir.live(); //Jeg er død

//Nå fokus
mann.ring(vladimir);
vladimir.live(); //Jeg lever


* Denne kildekoden ble uthevet med Source Code Highlighter.

Hva ser vi i dette eksemplet? For det første er det mulig å arve fra flere objekter som ikke er i samme hierarki. I eksemplet er det 2 av dem, men det kan være så mange du vil.
For det andre er det ikke noe hierarki i det hele tatt. Overstyring av egenskaper og metoder bestemmes utelukkende av rekkefølgen konstruktører kalles.
For det tredje er dette muligheten til å endre et objekt enda mer dynamisk, nærmere bestemt et individuelt objekt, og ikke alle dets etterkommere, som når du endrer en prototype.

Oppdatering: Nedleggelser og private eiendommer

For ikke å blåse opp denne allerede ganske store artikkelen, gir jeg en lenke til innlegget Closures in JavaScript, hvor dette er skrevet i noen detalj.

Hva skal man gjøre med alt dette nå

Som jeg sa ovenfor, er vilkårlig modifikasjon av individuelle objekter, bruk av konstruktører, mixins og fleksibiliteten til prototyper bare verktøy, muligheter som lar programmereren lage både forferdelig og fantastisk kode på alle måter. Det er bare viktig å forstå hvilke problemer vi løser, med hvilke midler, hvilke mål vi oppnår og hvilken pris vi betaler for det.

Dessuten er spørsmålet om pris ganske ikke-trivielt, spesielt hvis vi snakker om utvikling for nettleserversjonene Internet Explorer 6 og 7.
1. Minne - alt er enkelt her. I alle nettlesere tar nedarving på prototyper mye mindre minne enn når man lager metoder gjennom konstruktører. Dessuten, jo flere metoder og egenskaper vi har, jo mer forskjell. Det er imidlertid verdt å huske at hvis vi ikke har tusen identiske objekter, men bare én, så vil minneforbruket uansett være lite, fordi Det er andre faktorer å vurdere her.
2. Prosessortid - her er de viktigste finessene relatert spesifikt til nettlesere fra Microsoft.
På den ene siden kan objekter hvor metoder og egenskaper skapes gjennom en konstruktør lages mange ganger (i noen tilfeller titalls og hundrevis av ganger) langsommere enn gjennom en prototype. Jo flere metoder, jo tregere er det. Så hvis IE fryser i noen sekunder under skriptinitialisering, er det en grunn til å grave i denne retningen.

På den annen side kan et objekts egne metoder (de som er opprettet gjennom konstruktøren) utføres litt raskere enn prototypemetoder. Hvis du desperat trenger å fremskynde utførelsen av en bestemt metode i denne nettleseren, må du ta hensyn til dette. Husk at det er metodekallet (dvs. å søke etter det i objektet) som fremskyndes, ikke utførelsen. Så hvis selve metoden kjører et sekund, vil du ikke merke mye til en økning i ytelse.

I andre nettlesere lignende problemer Det er observert at tiden for å lage objekter og kalle metodene deres er omtrent den samme for begge tilnærmingene.

P.S. Vanligvis i artikler av denne typen tilbyr forfatteren en slags innpakning, enten ved å prøve å implementere klasseobjektarv basert på prototypisk arv, eller ganske enkelt syntaktisk sukker for prototypisk arv. Jeg gjør ikke dette med vilje, fordi... Jeg tror at en person som forstår betydningen av denne artikkelen er i stand til å skrive hvilken som helst innpakning for seg selv, og mange flere interessante ting :)

Tags: Legg til tagger