Niin monimutkaisilta kuin JavaScriptin suoritusaikaiset tietorakenteet ehkä näyttävätkin, oikeastaan kaiken takana on vain kolmenlaisia oliota: funktio-olioita, niihin liittyviä prototyyppiolioita ja "tavallisia" olioita. Kaikilla olioilla – paitsi yhdellä – on oma prototyyppinsä, josta se perii ominaisuuksia. Periytyminen on kuitenkin erilaista kuin luokkapohjaisissa oliokielissä.
Huom: Vaikka olion prototyyppikin on olio, tällä kurssilla käytämme sanaa prototyyppiolio tarkoittamaan vain funktio-olioon liitettyä oliota. Tuon sanan yhteydessä on aina mainittava minkä funktion prototyyppioliosta on kysymys!
Jokaisella funktio-oliolla on siis oma prototyyppiolionsa, joka kuitenkaan EI OLE kyseisen funktion oma prototyyppi – paitsi yhdellä funktiolla: Funktiolla Function prototyyppi ja prototyyppiolio ovat sama asia, koska Function-olio perii funktio-ominaisuutensa juuri omalta prototyyppiolioltaan!
Konstruktorifunktioiden prototyyppiolioista muodostetaan linkitettyjä ketjuja, joista oliot perivät ominaisuuksia. Normaalisti jokaisen olion prototyyppien ketju päättyy Object-funktion prototyyppiolioon.
Kuulostaako monimutkaiselta? Proosatekstin kirjoittaminen aiheesta on aika hankalaa! Kyllä asiat selvenevät, aletaan piirrellä...
assign: function assign() {
[native code]
}
getPrototypeOf: function getPrototypeOf() {
[native code]
}
setPrototypeOf: function setPrototypeOf() {
[native code]
}
getOwnPropertyDescriptor: function getOwnPropertyDescriptor() {
[native code]
}
...
...
seal: function seal() {
[native code]
}
isSealed: function isSealed() {
[native code]
}
fromEntries: function fromEntries() {
[native code]
}
prototype: [object Object]
length: 1
name: Object
Object.defineProperty(opiskelija, 'arvo', {writable: true});
Samaan tapaan myös itse ohjelmoitaviin konstruktorifunktioihin
voidaan ohjelmoida "staattisia" kenttiä ja
"luokkametodeita"!
toSource: function toSource() {
[native code]
}
toString: function toString() {
[native code]
}
toLocaleString: function toLocaleString() {
[native code]
}
valueOf: function valueOf() {
[native code]
}
...
...
__lookupGetter__: function __lookupGetter__() {
[native code]
}
__lookupSetter__: function __lookupSetter__() {
[native code]
}
__proto__: null
constructor: function Object() {
[native code]
}
kissantassu.toString()
prototype: function () {
}
length: 1
name: Function
write(Object.toString.call(Function) ) // function Function() {
// [native code]
// }
Function.prototype.constructor===Function Function.prototype.__proto__===Object.prototype Function.prototype===Function.__proto__Tuosta __proto__-kentästä kohta lisää.
toSource: function toSource() {
[native code]
}
toString: function toString() {
[native code]
}
apply: function apply() {
[native code]
}
call: function call() {
[native code]
}
bind: function bind() {
[native code]
}
isGenerator: function isGenerator() {
[native code]
}
arguments: null
caller: null
constructor: function Function() {
[native code]
}
length: 0
name:
var x = {a:1}
oK(Object.getPrototypeOf(x))
Tuloksena saadaan Object-konstruktorifunktion prototyyppiolio
Kyseessä on siis olio, johon Object.prototype-kenttä viittaa:
toSource: function toSource() {
[native code]
}
toString: function toString() {
[native code]
}
...
...
__proto__: null
constructor: function Object() {
[native code]
}
Tämä on juuri se olio, johon muiden olioiden prototyyppiketjut päättyvät
(ellei niitä ole itse katkaistu).
Lisäksi – kuten arvata saattaa:
var x = {a:1}
write(Object.getPrototypeOf(x)===x.__proto__) // true!
var x = {a:1}
oK(Object.getPrototypeOf(x))
ok(x.__proto__)
Object.getPrototypeOf(Object.prototype)===null
Object.prototype.__proto__===null
function F() {this.a=2}
var x = new F()
var y = new F()
Tämä on siis oman konstruktorifunktion F prototyyppiketju. F perii yleiset funktio-ominaisuutensa Function-funktion prototyyppioliolta ja sen jälkeen yleiset olio-ominaisuutensa Object-funktion prototyyppioliolta.
F-funktion prototype-kentän viittaaman F-funktion prototyyppiolion prototyyppi on siis Object.prototype-kentän viittaama Object-funktion prototyyppiolio. Kyseessä on siis "tavallinen" olio, ei funktio-olio.
Funktion F.prototype-kentän viittaamassa prototyyppioliossa on kenttä constructor, joka viittaa suoraan takaisin omaan funktio-olioonsa F. Näin ovat asiat kaikilla funktoilla, ellei linkitystä itse muuteta.
Olioiden x ja y prototyyppiketjun ensimmäinen prototyyppiolio on yhteinen, F.prototype-olio. Molemmilla on oma versionsa kentästä a. Muut ominaisuutensa ne perivät ketjusta F.prototype —> Object.prototype —> null.
function Yli() {this.a=2}
function Ali() {this.b=3}
Ali.prototype = new Yli() // tällä tekniikalla konstruktoriin
// liitetään "ylikonstruktori"
var x = new Ali()
var y = new Ali()
x.a = 77
Ali.prototype.constructor===Yli // kenttä constructor löytyy linkin päästä!
Ali.prototype.hasOwnProperty("constructor")===false
Ali-olion prototype-kentän viittaaman olion prototyyppiketju on siis: Yli.prototype —> Object.prototype —> null.
function Yli() {this.a=2}
function Ali() {this.b=3}
Ali.prototype = new Yli()
function AliAli() {this.c=4}
AliAli.prototype = new Ali()
var x = new AliAli()
kK(x) // kaikki kentät, omat ja perityt
Tulostus:
--------
c: 4
--------
b: 3
--------
a: 2
--------
constructor: function Yli() {this.a=2}
--------
toSource: function toSource() {
[native code]
}
toString: function toString() {
[native code]
}
...
...
__proto__: null
constructor: function Object() {
[native code]
}
function Elain() {
this.paino = 0
}
Elain.prototype.asetaPaino =
function (maara) {this.paino=maara}
Elain.prototype.syo =
function (maara) {this.paino+=maara}
Elain.prototype.kuluta =
function (maara) {this.paino-=maara}
function Tuotantoelain() {
this.tuotto = 0
}
Tuotantoelain.prototype = new Elain()
Tuotantoelain.prototype.asetaTuotto =
function (maara) {this.tuotto=maara}
Tuotantoelain.prototype.tuota =
function () {return this.tuotto}
function Lehma(nimi) {
this.nimi = nimi || "nimetön"; // kelvoton nimi => oletusarvoksi
} // (tämä on JavaScript-idiomi!)
Lehma.prototype = new Tuotantoelain()
Lehma.prototype.toString =
function () { return this.nimi+
": "+this.paino+" Kg, "+
this.tuotto+" litraa"
}
var m = new Lehma("Mansikki")
oK(m) // nimi: Mansikki (vain yksi oma kenttä!)
write(m) // Mansikki: 0 Kg, 0 litraa
m.asetaPaino(500)
oK(m) // nimi: Mansikki (kaksi omaa kenttää)
// paino: 500
write(m) // Mansikki: 500 Kg, 0 litraa
m.asetaTuotto(20)
m.syo(15)
oK(m) // nimi: Mansikki (kolme omaa kenttää)
// paino: 515
// tuotto: 20
write(m) // Mansikki: 515 Kg, 20 litraa
m.kuluta(100)
write(m) // Mansikki: 415 Kg, 20 litraa
write("Tuotetaan "+m.tuota()) // Tuotetaan 20
--------
nimi: Mansikki
paino: 415
tuotto: 20
--------
tuotto: 0
toString: function () { return this.nimi+
": "+this.paino+" Kg, "+
this.tuotto+" litraa"
}
--------
paino: 0
asetaTuotto: function (maara) {this.tuotto=maara}
tuota: function () {return this.tuotto}
--------
constructor: function Elain() {
this.paino = 0
}
asetaPaino: function (maara) {this.paino=maara}
syo: function (maara) {this.paino+=maara}
kuluta: function (maara) {this.paino-=maara}
--------
toSource: function toSource() {
[native code]
}
toString: function toString() {
[native code]
}
...
__proto__: null
constructor: function Object() {
[native code]
}
//... siis taas vanha tuttu Object.prototype
// eli Object-funktion prototyyppiolio
function Elain(kg) {
this.paino = kg || 0; // kelvoton paino => nollaksi
}
Elain.prototype.syo =
function (maara) {this.paino+=maara}
Elain.prototype.kuluta =
function (maara) {this.paino-=maara}
function Tuotantoelain(kg, litr) {
this.super = Elain
this.super(kg)
this.tuotto = litr || 0; // kelvoton tuotto => nollaksi
}
Tuotantoelain.prototype = new Elain()
Tuotantoelain.prototype.tuota =
function () {return this.tuotto}
function Lehma(nimi, kg, litr) {
this.super = Tuotantoelain
this.super(kg,litr)
this.nimi = nimi || "nimetön"; // kelvoton nimi => oletusarvoksi
}
Lehma.prototype = new Tuotantoelain()
Lehma.prototype.toString =
function () { return this.nimi+
": "+this.paino+" Kg, "+
this.tuotto+" litraa"
}
var m = new Lehma("Mansikki", 500, 12)
write(m) // Mansikki: 500 Kg, 12 litraa
kK(m)
-------- m -------------------------------
super: function Elain(kg) {
this.paino = kg || 0; // kelvoton paino => nollaksi
}
paino: 500
tuotto: 12
nimi: Mansikki
-------- Lehma.prototype -----------------
super: function Elain(kg) {
this.paino = kg || 0; // kelvoton paino => nollaksi
}
paino: 0
tuotto: 0
toString: function () { return this.nimi+
": "+this.paino+" Kg, "+
this.tuotto+" litraa"
}
-------- Tuotantoelain.prototype ----------
paino: 0
tuota: function () {return this.tuotto}
-------- Elain.prototype ------------------
constructor: function Elain(kg) {
this.paino = kg || 0; // kelvoton paino => nollaksi
}
syo: function (maara) {this.paino+=maara}
kuluta: function (maara) {this.paino-=maara}
-------- Object.prototype -----------------
toSource: function toSource() {
[native code]
}
toString: function toString() {
[native code]
}
...
__proto__: null
constructor: function Object() {
[native code]
}
//... siis taas vanha tuttu Object.prototype
// eli Object-funktion prototyyppiolio
function Elain(kg) {
this.paino = kg || 0; // kelvoton paino => nollaksi
}
Elain.prototype.syo =
function (maara) {this.paino+=maara}
Elain.prototype.kuluta =
function (maara) {this.paino-=maara}
function Tuotantoelain(kg, litr) {
Elain.call(this,kg)
this.tuotto = litr || 0; // kelvoton tuotto => nollaksi
}
Tuotantoelain.prototype = new Elain()
Tuotantoelain.prototype.tuota =
function () {return this.tuotto}
function Lehma(nimi, kg, litr) {
Tuotantoelain.call(this,kg,litr)
this.nimi = nimi || "nimetön"; // kelvoton nimi => oletusarvoksi
}
Lehma.prototype = new Tuotantoelain()
Lehma.prototype.toString =
function () { return this.nimi+
": "+this.paino+" Kg, "+
this.tuotto+" litraa"
}
var m = new Lehma("Mansikki", 500, 12)
write(m) // Mansikki: 500 Kg, 12 litraa
kK(m)
--------
paino: 500
tuotto: 12
nimi: Mansikki
--------
paino: 0
tuotto: 0
toString: function () { return this.nimi+
": "+this.paino+" Kg, "+
this.tuotto+" litraa"
}
--------
paino: 0
tuota: function () {return this.tuotto}
--------
constructor: function Elain(kg) {
this.paino = kg || 0; // kelvoton paino => nollaksi
}
syo: function (maara) {this.paino+=maara}
kuluta: function (maara) {this.paino-=maara}
--------
toSource: function toSource() {
[native code]
}
toString: function toString() {
[native code]
}
...
__proto__: null
constructor: function Object() {
[native code]
}
//... siis taas vanha tuttu Object.prototype
// eli Object-funktion prototyyppiolio
m instanceof Lehma m instanceof Tuotantoelain m instanceof Elain m instanceof Object
m.__proto__ —> Lehma.prototype Lehma.prototype.__proto__ —> Tuotantoelain.prototype Tuotantoelain.prototype.__proto__ —> Elain.prototype Elain.prototype.__proto__ —> Object.prototype Object.prototype.__proto__ —> null
var x = {a:1}
var y = Object.create(x)
var z = Object.create(x)
y.a=3
x.a=666
write(x.a) // 666
write(y.a) // 3
write(z.a) // 666 (!)
create-funktio on tietääkseni alunperin Douglas Crockfordin idea,
ks.hänen artikkelinsa
Prototypal Inheritance in JavaScript.
Hän esitti sen lisättäväksi ohjelmaan muodossa, jossa mahdollisesti
jo varusfunktiona toteutettua Object.create-funktiota ei
korvata:
if (typeof Object.create !== 'function') { // jos ei ole, lisätään
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
Vastaavalla tekniikalla voidaan pyrkiä varmistamaan että
jossain vanhassa selaimessakin on käytettävissä funktio, jota
oma sovellus käyttää!
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
ja tutkitaan, mitä äskeisessä esimerkissä tapahtuu.
var x = {a:1}
var y = {}
y.__proto__=x
var z = {}
z.__proto__=x
y.a=3
x.a=666
write(x.a) // 666
write(y.a) // 3
write(z.a) // 666 (!)
function Yli() {this.a=2}
function Ali() {this.b=3}
Ali.prototype = new Yli()
var x = new Ali()
var y = new Ali()
write(x.a) // 2
Ali.prototype.a=666
write(x.a) // 666
Tällaista ohjelmoija tuskin kuitenkaan tekee vahingossa!
var Animal = { traits: {} }
var lion = Object.create(Animal);
lion.traits.legs = 4;
var bird = Object.create(Animal);
bird.traits.legs = 2;
write(lion.traits.legs) // 2
function Animal() {this.traits = {} }
var lion = new Animal();
lion.traits.legs = 4;
var bird = new Animal();
bird.traits.legs = 2;
write(lion.traits.legs) // 4