JavaScript
Objekter
Børre Stenseth
Moduler>JavaScript>Egne Objekter

Klasser og objekter i Javascript

Hva
Egne objekter i Javascript

JavaScript er et objektorientert språk og vi kan lage våre egne objekter. Det er en del forhold som skiller objektbegrepet i JavaScript fra det vi kjenner i f.eks. Java. JavaScript er interpretert og JavaScript har ikke et sterkt typebegrep. Disse grunnleggende forskjellene manifesterer seg på mange måter, i syntaks og i "objekters livsløp".

Nedenfor skal vi se litt på noen av de sentrale karakteristika ved objekter i JavaScript. Hensikten er primært å gjøre objektorientert programmering tilgjengelig som praktisk verktøy i JavaScript, bidra til en forståelse av hvordan JS-biblioteker er bygget opp og hvordan vi bruker dem.

Objekter

Vi kan lage et enkelt objekt, ikke en klasse, på følgende enkle måte:

var ole={
	navn:"ole olsen",
	adresse:"moss",
	}

og

var T=ole.navn+' fra '+ole.adresse;

vil gi T : ole olsen fra moss

Vi kan også gjøre slik:

var per={
	navn:"per pettersen",
	adresse:"sarp",
	showMe:function(){return this.navn+' fra '+this.adresse;}
	}

og

var T=per.showMe();

vil gi T : per pettersen fra sarp

Hvis vi ser nærmere på formen vi har gitt til objektene,

{
  name:value,
  name:value,
}

så er det den samme formen som vi har som utgangspunkt når vis skal produsere JSON-tekst, se modulen JSON. Det betyr, uten at det er viktig i denne sammenhengen, at dersom vi har et objekt uten funksjoner, skal vi kunne klone objektet ole slik:

var jens=JSON.parse(JSON.stringify(ole));

Antagelig ikke særlig effektivt

Grunnformen er også den samme formen vi bruker når vi skal sende et objekt, eller om vi vil: et sett med verdier som parameter til en funksjon. F.eks. vil vi initiere et AJAX-kall i JQuery, se modulen , med noe slikt:

$.ajax(
  {
    url:address,
    async=False,
    success:function(data)
           {
             // do something
           },
    error:function(data)
          {
             // do something else
           },
  }
);

Antall "name:value"-par vil variere. Det er lett for mottageren å sjekke om en egenskap er satt ved ganske enkelt å spørre:

if (ole.showMe)
	alert(ole.showme())

Klasser

Vi så ovenfor på objekter. Nå skal vi se hvordan vi kan introdusere klasser ved hjelp av en konstruktor, som har formen av en funksjon.

Vi begynner med et enkelt eksempel.

Anta:

function person()
{
	var navn='ole';
	var adresse='halden';
}

Hvis vi nå skriver følgende:

var naboen = new person();

har vi satt opp et nytt object, naboen. Merk at vi altså oppretter objektet ved å kalle en funksjon. Funksjonskallet fungerer både som konstruktør og objektbeskrivelse, og det returnerer et objekt. De to egenskapene, navn og adresse, er lokale og vi har ikke stor glede av naboen. Dersom vi forsøker å få tak i naboen.navn, får vi beskjed om at dette er undefined. En var deklarasjon avgrenser variabelene til den lokale blokken, i dette tilfellet funksjonen eller, om vi vil, objektet.

Vi skriver om funksjonen person med tanke på at vi vil ha tilgang til egenskapene.

function person()
{
	this.navn='ole';
	this.adresse='halden';
}

dersom vi nå oppretter et objekt:

var naboen = new person();

kan vi skrive naboen.navn og få ole.

Vi kan videre lage følgende:

function person(nvn,adr)
{
	this.navn=navn;
	this.adresse=adr;
}

dersom vi nå oppretter et objekt:

var naboen = new person('Hans','Moss');

og naboen.adresse gir oss Moss.

Vi tar eksempelet et steg videre og forsøker å lage en konstruksjon med lokale egenskaper og synlige, public, metoder.

function person(nvn,adr)
{
	var navn=nvn;
	var adresse=adr;
	this.getNavn = function(){return navn}
	this.getAdresse = function(){return adresse}
}

dersom vi nå oppretter et objekt:

var naboen = new person('Jens','Sarp');

og naboen.getNavn() gir oss Jens.

Dersom vi lager følgende konstruksjon så har vi en ganske vanlig form, slik vi ofte ordner oss i Java, med lokale egenskaper og åpne metoder.

function person(nvn,adr)
{
	var navn=nvn;
	var adresse=adr;
	this.getNavn = function(){return navn}
	this.getAdresse = function(){return adresse}
	this.setNavn = function(nvn){navn=nvn}
	this.setAdresse = function(adr){adresse=adr}
}

Alternativt

Vi kan skrive følgende direkte:

var minbil = new Object();
minbil.merke = 'toyota';
minbil.farge = 'blå';

Det vil si at vi tilordner nye egenskaper til et nytt generelt objekt. Og vi kan fortsette med å plante en funksjon:

minbil.visfram = new function()
                  {
                    var vis=function()
                    {
                      return 'Det er en '+this.farge+' '+this.merke;
                    }
                    return vis;
                  };

Setningen:

minbil.visfram();

Vil gi resultatet: "Det er en blå toyota"

Dette viser et par interessante egenskaper ved Javascript. Den mekanismen som gjør at vi kan "plante" en funksjon kalles "closure". Det innebærer at funksjonen visfram kalles, den eksekveres og den returnerer en referanse til den interne funksjonen vis. Det er denne funksjonen, vis, som kjører når vi senere kaller visfram(). Denne mekanismen er nyttig og brukes utstrakt i ikke-trivielle javascriptbiblioteker. Blandt annet er denne mekanismen involvert i det minibiblioteket som er introdusert for enkel AJAX i modulen: Kodestrategier

Eksempelet ovenfor indikerer at vi skal kunne ta for oss et vilket som helst objekt, f.eks. i et bibliotek, og utvide objektet med nye egenskaper og metoder. Vi kan lager to person-objekter og gjør en av dem til bileier:

function person(nvn,adr)
{
	this.navn=nvn;
	this.adresse=adr;
	this.visfram=function(){return this.navn+' fra '+this.adresse;}
}

var person1=new person('Kristian','Askim');
var person2=new person5('Ole','Mysen');
person2.bilmerke='Ford';
person2.visfram = 
  new function()
     {
          var vis=function()
          {
               return this.navn+' fra '+this.adresse+' kjører '+this.bilmerke;
          }
          return vis;
     };
alert(person1.visfram())
alert(person2.visfram())

De to alertene gir hhv: "Kristian fra Askim" og "Ole fra Mysen kjører Ford".

Siden tester3.html viser hvordan vi kan plante og bruke en ny egenskap ved bildeobjekter, altså document.images.

../../dw/jseoop/tester3.htmlhttp://www.it.hiof.no/~borres/dw/jseoop/tester3.html

Den sentrale koden i eksempelet er:

	var imList=null;
	function setCopyRight()
	{
		return function()
		{
			return this.src+'<br/>CopyRight: '+this.owner;
		}
	}
	function prepareImages(navn)
	{
		imList=document.images;
		for(var ix=0;ix < imList.length;ix++)
		{
			imList[ix].owner=navn;
			imList[ix].copyRight=new setCopyRight();
		}
	}

Prototype og arv

Alle funksjoner, function-objekter, har en egenskap som heter prototype. Denne kan vi bruke til mye rart.

eksempel1

Vi kan lage følgende enkle konstruksjon:

function land(navn)
{
 this.land=navn;
}

function sted(hvor)
{
 this.by=hvor;
}

var byliste=new Array();

sted.prototype=new land('norge');
byliste[0]=new sted('moss');
byliste[1]=new sted('halden');

sted.prototype=new land('sverige');
byliste[2]=new sted('uddevalla');

sted.prototype=new land('danmark');
byliste[3]=new sted('aalborg');

En utskrift av byliste, byliste[ix].by+' i '+byliste[ix].land, vil gi oss

moss i norge
halden i norge
uddevalla i sverige
aalborg i danmark

Greitt nok, men gevisnten er ikke stor. Vi ville oppnådd akkurat det samme med å la landsnavnet være en egenskap ved stedet i en flat klassestruktur.

eksempel 2

La oss se på hvordan vi kan subklasse et land til republikk eller kongedømme:

function land(nvn)
{
 this.navn=nvn;
 this.show=function(){return this.navn};
}
function republikk(nvn)
{
 this.prototype=new land(nvn);
 this.show=function()
     {
        return this.prototype.show()+ ' som er en republikk'
     };
 }
function kongerike(nvn)
{
 this.prototype=new land(nvn);
 this.show=function()
     {
        return this.prototype.show()+ ' som er et kongedømme'
     };
}

var landliste=new Array();

landliste[0]=new kongerike('norge');
landliste[1]=new kongerike('danmark');

landliste[2]=new republikk('frankrike');
landliste[3]=new republikk('italia');

Som gir ( landliste[ix].show() ):

norge som er et kongedømme
danmark som er et kongedømme
frankrike som er en republikk
italia som er en republikk

Vi kunne oppnådd det samme ved å skrive:

function land(nvn)
 {
   this.navn=nvn;
   this.show=function(){return this.navn};
 }
 function republikk(nvn)
 {
   this.prototype=republikk.prototype;
   this.navn=nvn;
   this.show=function()
      {
         return prototype.navn+' som er en republikk'
      };
 }
 function kongerike(nvn)
 {
   this.prototype=kongerike.prototype;
   this.navn=nvn;
   this.show=function()
      {
         return prototype.navn+' som er et kongedømme'
      };
 }

 var landliste=new Array();

 kongerike.prototype=new land();
 republikk.prototype=new land();

 landliste[0]=new kongerike('norge');
 landliste[1]=new kongerike('danmark');

 landliste[2]=new republikk('frankrike');
 landliste[3]=new republikk('italia');

Vi ser at dette åpner for at vi dynamisk kan endre hva som er prototype for en klasse, altså hvilken klasse vi ønsker å subklasse.

eksempel 3

Vi kan ta for oss personer og bileiere. Vi tenker oss altså at bileier skal være en subklasse av person. Vi kan da lage følgende konstruksjon:

function person(nvn,adr)
{
	this.navn=nvn;
	this.adresse=adr;
	this.show=function(){return this.navn+' fra '+this.adresse}
}
function bileier(mrk,frg,nvn,adr)
{
	this.merke=mrk;
	this.farge=frg;
	this.prototype=new person(nvn,adr);
	this.show=function()
	{
	  return this.prototype.show()+' kjører en ' +this.farge+' '+this.merke
	}
}

og vi kan bruke det til å lage en array av personer, der noen er bileiere, slik:

var liste=new Array();
liste[1]=new bileier('toyota','blå','ole','moss');
liste[2]=new bileier('ford','gul','hans','mysen');
liste[3]=new bileier('vw','grønn','gudrun','marker');
liste[3].prototype=new person('kristian','sarpsborg');
liste[4]=new bileier('opel','hvit');
liste[4].prototype=new person('marit','askim');
liste[5]=new person('sverre','rakkestad');

En utskrift av lista, liste[i].show(), vil gi følgende:

ole fra moss kjører en blå toyota
hans fra mysen kjører en gul ford
kristian fra sarpsborg kjører en grønn vw
marit fra askim kjører en hvit opel
sverre fra rakkestad

Merk liste[3], som "bytter eier", altså får ny person. I andre språk ville vi vel kalle dette dynamisk subklassing.

Merk også liste[4], som etableres uten eier. Vi ser litt nærmere på dette. Hvis vi skriver:

p=new bileier('opel','hvit');
alert(p.show());
p.prototype=new person('marit','askim');
alert(p.show());

vil vi i tur og orden få:

 undefined fra undefined kjører en hvit opel
 marit fra askim kjører en hvit opel

eksempel 4

Endre prototype for en std klasse.

Javascript har ikke noen funksjoner for å trimme text. Det vil si å fjerne whitespace fra begge eller en av endene på en string. Det er ganske nyttig å ha tilgang på denne funksjonaliteten når vi f.eks. skal fjerne tomme linjer i starten eller slutten av en tekst. Følgende løsning basert på regexp finnes i litt forskjellige varianter på nettet:

String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g,"");
}
String.prototype.ltrim = function() {
	return this.replace(/^\s+/,"");
}
String.prototype.rtrim = function() {
	return this.replace(/\s+$/,"");
}

Når disse funksjonene er introdusert vil de være virksomme for alle stringer og vi kan skrive:

var T='\t   Olsen \n\n\n';
alert('|'+T.trim()+'|');

og få ut:

JSON

En drøfting av praktisk bruk av objektorientert javaskripting må ta med JSON (JavaScript Object Notation) som en viktig komponent. I dette materialet er JSON beskrevet i en egen modul, JSON. Der er fokus satt på JSON som en effektiv måte å overføre og tolke data i forbindelse med AJAX-løsninger.

Referanser
  1. JSON (JavaScript Object Notation)json.orgwww.json.org/14-03-2010
Vedlikehold

Børre Stenseth, modifisert juli 2012

(Velkommen) Moduler>JavaScript>Egne Objekter (å lese forms)