Hopp til innholdet

cjohansen.no

Arrayer, funksjoner og objekter

Mer grunnleggende JavaScript: vi fortsetter fra igår og ser på arrayer, funksjoner og objekter i JavaScript.

Objekter

Arrayer og funksjoner er objekter, så vi starter med objektene. Objekter i JavaScript er dynamiske - du kan sette nye egenskaper på dem etter eget forgodtbefinnende.

var obj = new Object(); // Tomt objekt
var obj2 = {}; // Tomt objekt
var obj3 = {
    name: "JS",
    getName: function() {
        return this.name;
    }
}; // Now mer "vanlig" objekt
obj2.name = "Navn"; // Legg til egenskap på eksisterende objekt

"Literaler"

I programmering er det enkelte verdier som er "literals" - eller bokstavelige. Med dette mener vi verdier vi kan skrive rett inn i programmet vårt:

var str = "En strengliteral";
var int = 32; // tall-literal
var boolean = true; // boolean-literal
var obj = {}; // Objekt-literal
var obj2 = { someProp: "some value" }; // ...ditto
var arr = []; // Array-literal
var arr2 = [1, 2, 3]; // ...ditto

JavaScript har altså "objektliteraler". Disse danner også basis for JSON - som da også står for "JavaScript Object Notation".

I JavaScript-objekter kan property-navnene være hvilken som helst type objekter, men - de konverteres til strenger internt, så vær obs på at selvom 7 og "7" er forskjellige objekter så er de den samme strengen:

var obj = { 7: "Dette er verdien for tallet 7",
            "7": "Dette er verdien for strengen '7'" };
obj['7']; // "Dette er verdien for strengen '7'"

Her ser vi at verdien for strengen '7' overskrev verdien for tallet 7. Dette skjer som sagt fordi JavaScript kun kan ha strenger som navn på properties/egenskaper. Hvis du ønsker å tydeliggjøre dette kan du kode nøklene eksplisitt som strenger: var obj = { 'name': 'Johan' };

For å aksessere properties/egenskaper på et objekt kan du enten bruke dot-notasjon: obj.property eller du kan aksessere egenskaper som om de var nøkler i en hash/assosiativ array: obj['property'].

NB! Selvom det ser sånn ut så er ikke objekter i JavaScript hasher.

Prototyper

JavaScript-objekter er ikke instanser av klasser. JavaScript har ikke klasser - det har prototyper. En prototype er bare et vanlig objekt som brukes som utgangspunkt for et annet objekt. Har du god tid så har Steve Yegge en veldig god forklaring av konseptet.

Steve Yegge forklarer konseptet med følgende eksempel (tilgi eksempelet fra den eksotiske sporten amerikansk fotball, men jeg kan ikke nok om hverken amerikansk eller europeisk fotball til å "lokalisere" eksempelet):

Se for deg at du ser en amerikansk fotballkamp, og kommentatorene prater om en ny fotballspiller du ikke har hørt om før. Denne karen, "L. T." er på nåværende tidspunkt en instans av klassen "fotballspiller" uten noen særtrekk.

Etterhvert nevner kommentatorene at L.T. er en "running back", akkurat som Emmitt Smith - han har god fart og balanse, og han kan det å finne hull i forsvaret. Nå har vi kommet til det punktet hvor L.T. faktisk er en "instans" av (eller en klone av) Emmitt Smith: han arvet alle Emmitts egenskaper - ihvertfall de du kjenner til.

Etterhvert kommer det flere opplysninger: L.T. spiller også av og til som "wide receiver", han bruker hjelm, og han løper litt som Walter Payton. Etterhvert som kommentatorene legger til karakteriserende opplysninger tar L.T. gradvis form som en spesifik entitet som blir mindre og mindre avhengig av sin klasse "fotballspiller". Han er nå blitt en veldig spesifik fotballspiller.

Også kommer trikset: selvom L.T. nå er en spesifik instans kan du nå bruke også ham som en klasse! La oss si at "Joe the Rookie" slår seg opp året etter. Kommentatorene introduserer det nye stjerneskuddet som en som "ligner mye på L.T.". Helt uten videre har Joe nå arvet alle egenskapene til L.T., og Joe kan ta spesifikke verdier for enkelte av L.T.s egenskaper og sakte men sikkert bli sin egen unike instanse av en fotballspiller.

Dette er prototype-basert modellering, modellen som JavaScript baserer sine objekter på. I eksempelet vårt er Emmitt Smith prototypen for L.T., og L.T. ble i sin tur prototypen til Joe, som i sin tur kan være noen andres prototype.

Fritt oversatt fra Steve Yegge. Les hele artikkelen en dag du har litt tid (den er lang).

Hvordan funker dette i JavaScript

Teori er vel og bra, men la oss se hva dette betyr for oss i praksis. I JavaScript har alle objekter en prototype. Dersom du ikke spesifiserer annet er denne prototypen Object, JavaScripts generiske objekt. Ethvert objekt kan tjene som et annet objekts prototype.

Selvom konseptet her er relativt simpelt så er syntaksen for akkurat dette noe klønete:

// Konstruktør som lager et nytt person-objelk med et navn
var Person = function(name) {
    this.name = name;
};

// Legg en metode på prototypen - denne blir tilgjengelig for alle Person-objekter
Person.prototype.getName = function() {
    return this.name;
};

var Programmer = function(name, preferredLanguage) {
    this.name = name;
    this.preferredLanguage = preferredLanguage;
};

// En programmerer er et spesielt type menneske
Programmer.prototype = new Person();

var christian = new Programmer("Christian", "Ruby");
console.log(christian.getName()); // "Christian"

Her lager jeg to objekter, Person og Programmer. Person har en Object-instans som prototype, mens Programmer har en Person-instans som sin prototype. Sammen danner disse en prototypekjede som går fra et Programmer-objekt "opp" til et Object.

Når du spør etter en egenskap på et objekt så gjør JavaScript følgende:

  1. Sjekk objektet selv
  2. Sjekk objektets prototype
  3. Sjekk prototypens prototype
  4. osv til en Object-instans er nådd

For vår Programmer betyr dette at når jeg gjør christian.getName() så sjekker JavaScript Programmer, finner ingenting, og henvender seg til prototypen Person og lykkes. Hadde jeg bedt om christian.toString() hadde jeg fått svar fra Object-instansen. Det ligner på klasser og klassisk arv, men det er noe prinsipielt helt annerledes.

For kort tid siden skrev jeg en lengre artikkel om objektorientert JavaScript. Jeg kommer også tilbake til temaet senere i julekalenderen.

Arrayer

JavaScript opererer ikke med arrayer på helt samme måte som du kanskje er vant til. JavaScript jukser faktisk, og bruker et objekt. Indeksene blir konvertert til strenger, og brukes som properties:

var arr = [2, 3, 4, 5];
console.log(arr[2] === arr['2']); // true

Ok, det var kanskje en litt negativ start. JavaScript-arrayer har endel array-funksjonalitet, og JavaScript har jo som vi har sett array-literaler.

Lengde

Lengden på en array er tilgjengelig gjennom propertien (ikke metoden) length. Dette er ikke en absolutt verdi; legger du til noen elementer økes bare størrelsen:

var arr = [1, 2];
console.log(arr.length); // 2
arr[arr.length] = 3;
arr.push(4);
console.log(arr.length); // 4

Sletting

Du kan slette ett element fra en JavaScript-array med delete:

var arr = [1, 2, 3];
delete arr[1];
console.log(arr); // [1,,3]
console.log(arr.length); // 3
console.log(arr[1]); // undefined

Looping

Arrayer kan, i kraft av å være objekter, loopes med for in, men det er ikke å anbefale. Du har ingen garanti for rekkefølgen på elementene, og dersom du har gitt et objekt (eller kanskje Array.prototype) en eller flere nye metoder/egenskaper, så vil du få med disse også. Arrayer bør derfor loopes på den tradisjonelle måten:

var arr = [1, 2, 3, 4], i;

for (i = 0; i < arr.length; i++) {
    console.log(arr[i]);
}

// Alternativt:
var element;

for (i = 0; (element = arr[i]); i++) {
    console.log(element);
}

Den siste fungerer fordi arr[i] blir undefined når du har kommet forbi arr.length. undefined er som vi vet en "falsy" verdi. Vær obs! Dersom arrayen din inneholder andre falsye verdier vil de også stoppe løkka!

var arr = [1, 2, 0, 4];

for (var i = 0, element; (element = arr[i]); i++) {
    console.log(element);
}

// Skriver ut:
// 1
// 2

Bruk altså denne loopen med omhu!

Her er litt diverse nyttig array-funksjonalitet:

var arr = [1, 2, 3];
console.log(arr[0]); // 1
arr.push("Hei"); // arr == [1, 2, 3, "Hei"]
console.log(arr.pop()); // 1, arr == [2, 3, "Hei"]
var arr2 = new Array(10); // Ny array med 10 tomme plasser
arr[arr.length] = 4; // arr == [2, 3, "Hei", 4]
arr[10] = -1; // arr == [2, 3, "Hei", 4,,,,,,, -1]

// Slice:
arr = [1, 2, 3, 4, 5, 6, 7, 8];
arr = arr.slice(3, 6); // arr == [4, 5, 6]

Funksjoner

Funksjoner

function enVanligFunksjon(parameter1, parameter2) {
    return parameter1 + parameter2;
}

var obj = {};

obj.enMetode = function() {
    return this.name;
};

Funksjoner kan ha navn. Dersom de ikke har det (eksempel 2) kalles de anonyme funksjoner. Slike funksjoner må tilordnes en variabel, eller et objekt for å kunne kalles senere. Anonyme funksjoner kan være tricky å debugge i stack trace:

var obj = {};
obj.myMethod = function() {
    // Kode
};

console.log(obj.myMethod); // "" - logger ofte som "function" i konsollet

// Setter navn på funksjonen - merk at det ikke trenger å være samsvar
// mellom funksjonsnavnet og variabelen som funksjonen er lagret i, selvom
// det er tilfelle her
obj.myMethod = function myMethod() {
    // Kode
};

console.log(obj.myMethod); // "myMethod"

Merk at nøkkelpunktet her er at metoden vet ingenting om variabelen/objektegenskapen den er tilordnet til.

Returverdier

Metoder kan returnere verdier med return. Dersom de ikke returnerer noe vil verdien evaluere som undefined - i sannhetsuttrykk blir dette "falsy".

Konstruktører

Jeg touchet såvidt innom disse i array-eksemepelet. Selvom JavaScript ikke har klasser så har det konstruktører. En konstruktør er en JavaScript-funksjon som kalles med nøkkelordet new, og dermed lager et nytt objekt. Det er ingen forskjeller på en funksjon og en konstruktør, det er bruken som skiller dem:

var Person = function(name) {
    this.name = name;
}

var christian = new Person("Christian");
console.log(christian.name); // "Christian"

var johan = Person("johan");

try {
    console.log(johan.name);
} catch (e) {
    console.log(e.message); // "johan is undefined"
}

console.log(window.name); // "johan"

//Ooops, når Person ikke var en konstruktør var this === window

Det eneste som skiller en vanlig funksjon fra en konstruktør er at konstruktører har navn som starter med stor bokstav - men dette er kun en konvensjon.

En funksjon kan også være både og. Dette kan du benytte til å sikre at en funksjon alltid er en konstruktør for eksempel:

var Person = function(name) {
    if (!(this instanceof Person)) {
        return new Person(name);
    }

    this.name = name;
}

var christian = Person("Christian");
console.log(christian.name); // "Christian"

this, arguments og mer om funksjoner

Jeg har tidligere skrevet en lengre forklaring på JavaScripts dynamiske this. arguments møter vi igjen senere i kalenderen når vi ser nærmere på funksjonsparametere. Funksjoner generelt blir det også mer stoff om i desember.

Vel møtt til litt info om null, undefined og NaN i morgen.

Muligens relatert

2006 - 2010 Christian Johansen Creative Commons Lisens