let retki = {matka: 150, aika: 2, nopeus: function() {return this.matka/this.aika} } write(retki.nopeus()); // 75
let matka=20, aika=10 let retki = {matka: 150, aika: 2, nopeus: function() {return matka/aika} } write(retki.nopeus()); // 2 !!
let retki = {matka: 150, aika: 2, nopeus: function() {return this.matka/this.aika} } oK(retki)tulostaa:
matka: 150 aika: 2 nopeus: function () {return this.matka/this.aika} --------
-------- matka: 150 aika: 2 nopeus: function() {return this.matka/this.aika} -------- toSource: function toSource() { [native code] } toString: function toString() { [native code] } toLocaleString: function toLocaleString() { [native code] } valueOf: function valueOf() { [native code] } ... ... paljon, paljon muita kenttiä ... __lookupGetter__: function __lookupGetter__() { [native code] } __lookupSetter__: function __lookupSetter__() { [native code] } __proto__: null constructor: function Object() { [native code] }Tästä nähdään, miten olio retki perii Object-funktion ns. prototyyppiolion. Se siis saa olioiden yhteiset ominaisuudet tuolta "ylioliolta", omalta prototyypiltään. Lupaan että asiaan palataan! ;-)
[ .......Tästä tilanteesta piirretään kuvasimulaatio luennolla! ....... ]
let retki = new Object(); retki.matka = 150; retki.aika = 2; retki.nopeus = function() {return this.matka/this.aika} write(retki.nopeus()); // 75
let retki = {matka: 150, aika: 2, nopeus: function() {return this.matka/this.aika} } retki.metka = 666 oK(retki)Tulostus:
matka: 150 aika: 2 nopeus: function () {return this.matka/this.aika} metka: 666 --------
let retki = {matka: 150, aika: 2, nopeus: function() {return this.matka/this.aika} } write(retki.pituus) // undefined
let retki = {matka: 150, aika: 2, nopeus: function() {return this.matka/this.aika} } delete retki.aika; oK(retki) write(retki.aika) write(retki.nopeus())Tulostus:
matka: 150 nopeus: function () {return this.matka/this.aika} -------- undefined NaN
let retki = {matka: 150, aika: 2, nopeus: function() {return this.matka/this.aika} } write(retki.matka); // 150 retki.aika = -3.14; write(retki.aika); // -3.14 write(retki.nopeus()) // -47.77070063694267 retki.nopeus=function() {return 666} write(retki.nopeus()) // 666
Jos vaikkapa Object-funktion prototyyppiolioon, johon osoittaa kenttä Object.prototype, lisätään vaikkapa jokin uusi taito, kaikki oliot perivät tuon taidon! (Prototyypeistä puhutaan enemmän seuraavissa luvuissa. Nyt kiinnostavaa on se, että jopa tuo mitä perustavanlaatuisin Object.prototype on julkinen ja muunnettava kenttä siinä missä mikä tahansa muukin kenttä!)
let retki = {matka: 150, aika: 2, nopeus: function() {return this.matka/this.aika} } Object.prototype.boo = function() {write("BÖÖ!")}; retki.boo(); // BÖÖ! var d = new Date(); d.boo(); // BÖÖ! Function.boo() // BÖÖ! Object.boo() // BÖÖ!Tässä kohdassa ehkä kannattaa vähän säikähtää!
Esimerkiksi kaikkien olioiden, funktioden ja taulukoiden tulostusasu (so. toString-metodi) voidaan korvata omalla (paremmalla?) versiolla:
let retki = {matka: 150, aika: 2, nopeus: function() {return this.matka/this.aika} } Object.prototype.toString = () => "BÖÖ!" Function.prototype.toString = () => "HEISSAN!" Array.prototype.toString = () => "HÖLÖKYNKÖLÖKYN" write(retki) // BÖÖ! write(retki.nopeus) // HEISSAN! write({a:1,b:2}) // BÖÖ! write(x=>x*x) // HEISSAN! write([1,2,3]) // HÖLÖKYNKÖLÖKYNNyt sitten kannattaa säikähtää ihan tosissaan!
let retki = {matka: 150, aika: 2, nopeus: function() {return this.matka/this.aika} } write(retki.nopeus()); // 75ja kenttien lisääminen Object-olioon:
let retki = new Object(); retki.matka = 150; retki.aika = 2; retki.nopeus = function() {return this.matka/this.aika} write(retki.nopeus()); // 75
function Matkailu(matka,aika) { this.matka = matka; this.aika = aika; this.nopeus = function() {return this.matka/this.aika} } let retki = new Matkailu(150,2); write(retki.matka); // 150 write(retki.nopeus()); // 75 let f1 = new Matkailu(150,0.5); write (f1.aika); // 0.5 write(f1.nopeus()); // 300
[ .......Tästä tilanteesta piirretään kuvasimulaatio luennolla! ....... ]
function Matkailu(matka,aika) { this.matka = matka; this.aika = aika; } Matkailu.prototype.nopeus = function() {return this.matka/this.aika} let retki = new Matkailu(150,2); write(retki.nopeus()); // 75 let f1 = new Matkailu(150,0.5); write(f1.nopeus()); // 300
write(retki.propertyIsEnumerable()); // false
[ ....... Näistä tilanteista piirretään kuvasimulaatio luennolla! ....... ]
function Matkailu(matka,aika) { this.matka = matka; this.aika = aika; } Matkailu.prototype.nopeus = function() {return this.matka/this.aika} let retki = new Matkailu(150,2); kK(retki)Ohjelma tulostaa:
-------- matka: 150 aika: 2 -------- constructor: function Matkailu(matka,aika) { this.matka = matka; this.aika = aika; } nopeus: function() {return this.matka/this.aika} -------- toSource: function toSource() { [native code] } toString: function toString() { [native code] } toLocaleString: function toLocaleString() { [native code] } valueOf: function valueOf() { [native code] } hasOwnProperty: function hasOwnProperty() { [native code] } isPrototypeOf: function isPrototypeOf() { [native code] } propertyIsEnumerable: function propertyIsEnumerable() { [native code] } __defineGetter__: function __defineGetter__() { [native code] } __defineSetter__: function __defineSetter__() { [native code] } __lookupGetter__: function __lookupGetter__() { [native code] } __lookupSetter__: function __lookupSetter__() { [native code] } __proto__: null constructor: function Object() { [native code] }Oliossa retki näkyy siis olevan juuri ne kentät, jotka konstruktorifunktio sinne konstruoi.
Lähimmässä retki-olion prototyypissä eli Matkailu-funktion prototyyppioliossa on kentät constructor ja nopeus. Jälkimmäinen sinne lisättiin itse.
Ja seuraavaksi sitten löytyykin jo Object-funktion monikenttäinen prototyyppiolio. Jos et usko, suorita oK(Object.prototype)!
function Olio(a) { this.a = a; } Olio.prototype.b = 33; Olio.prototype.c = {d: 44} let x = new Olio(55) /* Kokeile näitä! (yksittäin tai yhdistettynä) oK(x) oK(x.__proto__) oK(x.__proto__.c) oK(x.__proto__.c.__proto__) oK(x.__proto__.c.__proto__.constructor) */ var y = new Olio(66) write(y.a) x.b = 77 write(y.b) x.c.d = 88 write(y.c.d) x.c = "kissa"
[ Ohjelman suoritus simuloidaan luennolla! ....... ]
Mielenkiintoinen ero, eikö totta! Vertaa tilannetta staattisten kielten vastaavaan luokkamuuttujien tapaukseen.
Esimerkiksi edellä nähty "vanhan tyylin" ohjelma
function Matkailu(matka,aika) { this.matka = matka; this.aika = aika; } Matkailu.prototype.nopeus = function() {return this.matka/this.aika} let retki = new Matkailu(150,2); write(retki.nopeus());voidaan kirjoittaa staattisista kielistä tutun näköiseen tapaan:
class Matkailu { constructor(matka,aika) { this.matka = matka; this.aika = aika; } nopeus() { return this.matka/this.aika } } let retki = new Matkailu(150,2); write(retki.nopeus());
class Luokka { static luokkametodi() { write("Olen luokkametodi.")} ilmentymametodi() { write("Olen ilmentymämetodi.")} } Luokka.luokkametodi(); let x = new Luokka x.ilmentymametodi() write("\n******* Luokan kentät ********") oK(Luokka) write("\n******* x:n kentät ********") oK(x) write("\n******* x.__proto__:n kentät ********") oK(x.__proto__)Ohjelma tulostaa:
Olen luokkametodi. Olen ilmentymämetodi. ******* Luokan kentät ******** prototype: [object Object] luokkametodi: luokkametodi() { write("Olen luokkametodi.")} length: 0 name: Luokka -------- ******* x:n kentät ******** -------- ******* x.__proto__:n kentät ******** constructor: class Luokka { static luokkametodi() { write("Olen luokkametodi.")} ilmentymametodi() { write("Olen ilmentymämetodi.")} } ilmentymametodi: ilmentymametodi() { write("Olen ilmentymämetodi.")} --------
function KonstLuokka() { } KonstLuokka.luokkametodi = () => write("Olen luokkametodi.") KonstLuokka.prototype.ilmentymametodi = () => write("Olen ilmentymämetodi.") KonstLuokka.luokkametodi(); let x = new KonstLuokka() x.ilmentymametodi() write("\n******* KonstLuokan kentät ********") oK(KonstLuokka) write("\n******* x:n kentät ********") oK(x) write("\n******* x.__proto__:n kentät ********") oK(x.__proto__)Tämä ohjelma tulostaa lähes saman rakenteen:
Olen luokkametodi. Olen ilmentymämetodi. ******* KonstLuokan kentät ******** luokkametodi: () => write("Olen luokkametodi.") prototype: [object Object] length: 0 name: KonstLuokka -------- ******* x:n kentät ******** -------- ******* x.__proto__:n kentät ******** constructor: function KonstLuokka() { } ilmentymametodi: () => write("Olen ilmentymämetodi.") --------
var retki = {matka: 150, aika: 2, nopeus: function() {return this.matka/this.aika} } write(retki["matka"]); // 150 retki["aika"] = -3.14; write(retki["aika"]); // -3.14 write(retki["nopeus"]()); // -47.77070063694267 write(retki["hasOwnProperty"]()); // false
var retki = {matka: 150, aika: 2, nopeus: function() {return this.matka/this.aika} } var x = "ai" + "ka"; write(retki[x]); // 2 write(retki[prompt("Mikä kenttä?")]); // Kokeile tätä eri syöttein!
var otus = { tunnus: 11, "mjono": 22, "": 33, 128: 44, 3.14: 55, "yks juttu": 66, 1: 77 } oK(otus)Tulostus:
1: 77 128: 44 tunnus: 11 mjono: 22 : 33 3.14: 55 yks juttu: 66 --------Asetellaan arvoja:
otus[3.14] = 123 otus.tunnus = -12 otus[1] = 2.4e-100; otus[""] = "Ahaa!" oK(otus)Tulostus:
1: 2.4e-100 128: 44 tunnus: -12 mjono: 22 : Ahaa! 3.14: 123 yks juttu: 66 --------
(En ole keksinyt, mikä tuo kenttien tulostuksen läpikäyntijärjestys on! Tavalla tai toisella se liittynee kenttänimien String-esitykseen? Hmm. referenssi sanoo "arbitary order"... Lienee tarkoituksella jätetty toteutusriippuvaksi?)
var o = { tunnus: 11, "tunnus": 22, tunnus: 33 } oK(o) // tunnus: 33 // --------
var olio = {a:1, b:2, c:3, d: function(x) {this.b+=x}}; olio.d(100) for (kentta in olio) write(kentta + ": " + olio[kentta]);Tulostus:
a: 1 b: 102 c: 3 d: function (x) {this.b+=x}
Tässä esimerkissä ei näy perityn yhtään numeroituvaa attribuuttia.
Object.defineProperty(olio, 'd', { value: function(x) {this.b+=x}, enumerable: false });
Edellinen esimerkki muunnettuna:
var olio = {a:1, b:2, c:3, d: function(x) {this.b+=x}}; olio.d(100) for (kentta in olio) write(kentta + ": " + olio[kentta]); // kenttä d tulee mukaan write("--------") Object.defineProperty(olio, 'd', { value: function(x) {this.b+=x}, enumerable: false }); for (kentta in olio) write(kentta + ": " + olio[kentta]); // NYT KENTTÄ d EI OLE MUKANATällaisella menettelyllä on selvästi käyttöä, esim.
var opiskelija = { nimi: "Muumi Papanpoika", arvo: "Fil.yo", koepisteet: 24, harjpist: 11, bonuspist: 8 } Object.defineProperty(opiskelija, 'nimi', {enumerable: false }); Object.defineProperty(opiskelija, 'arvo', {enumerable: false }); var pisteet = 0; for (kentta in opiskelija) pisteet += opiskelija[kentta]; write(pisteet); // tulostuu kauniisti 43 // jos nimi- ja arvokenttä olisi numeroituva, // tulostuisi 0Muumi PapanpoikaFil.yo24118
var x = {} // tyhjä olio Object.defineProperty(x, "a", // lisätään ominaisuus {value: 101, writable: false, enumerable: true, configurable: true} ) write(x.a) // 101 x.a=2 write(x.a) // 101
var opiskelija = { nimi: "Muumi Papanpoika", arvo: "Fil.yo", koepisteet: 24, harjpist: 11, bonuspist: 8 } Object.defineProperty(opiskelija, 'arvo', {writable: false, enumerable: false }); opiskelija.arvo = "maisteri" write(opiskelija.arvo); // Fil.yoAidossa JavaScript-hengessä tuota kenttää saa muuttaa niin paljon kuin sielu sietää, mutta eihän se siitä miksikään muutu...
Mutta eipä syytä huolestua, maisteriksi voi silti päästä kun ensin osaa vähän komennella: ;-)
Object.defineProperty(opiskelija, 'arvo', {writable: true}); opiskelija.arvo = "maisteri" write(opiskelija.arvo); // maisteri :-)
var olio = {a:1, b:2, c:3, d: function(x) {this.b+=x}}; Object.defineProperty(olio, 'd', {enumerable: false }); write(Object.keys(olio)); // a,b,c write(Object.getOwnPropertyNames(olio)); // a,b,c,d
function kaikkiKentat(o) { for(var olio = o; olio !== null; olio = Object.getPrototypeOf(olio)) { write("--------"); write(Object.getOwnPropertyNames(olio)); } }
Tutkitaan muutamien olioiden kaikkien kenttänimien joukkoa:
Pelkkä "tietue":
var o = {a:1, b:2}; kaikkiKentat(o); -------- a,b -------- toSource,toString,toLocaleString,valueOf,hasOwnProperty,isPrototypeOf, propertyIsEnumerable,__defineGetter__,__defineSetter__,__lookupGetter__, __lookupSetter__,__proto__,constructor
Olio jossa on myös funktio:
var olio = {a:1, b:2, c:3, d: function(x) {this.b+=x}}; kaikkiKentat(olio); -------- a,b,c,d -------- toSource,toString,toLocaleString,valueOf,hasOwnProperty,isPrototypeOf, propertyIsEnumerable,__defineGetter__,__defineSetter__,__lookupGetter__, __lookupSetter__,__proto__,constructor
Tyhjällä uudella oliolla on vain prototyyppinsä kentät:
kaikkiKentat(new Object()); -------- -------- toSource,toString,toLocaleString,valueOf,hasOwnProperty,isPrototypeOf, propertyIsEnumerable,__defineGetter__,__defineSetter__,__lookupGetter__, __lookupSetter__,__proto__,constructorKenttiähän riittää ...
var olio = {a:1, b:2, c:3, d: function(x) {this.b+=x}}; oK(olio.__proto__)Mitäs sieltä löytyykään:
toSource: function toSource() { [native code] } toString: function toString() { [native code] } toLocaleString: function toLocaleString() { [native code] } valueOf: function valueOf() { [native code] } hasOwnProperty: function hasOwnProperty() { [native code] } isPrototypeOf: function isPrototypeOf() { [native code] } propertyIsEnumerable: function propertyIsEnumerable() { [native code] } __defineGetter__: function __defineGetter__() { [native code] } __defineSetter__: function __defineSetter__() { [native code] } __lookupGetter__: function __lookupGetter__() { [native code] } __lookupSetter__: function __lookupSetter__() { [native code] } __proto__: null constructor: function Object() { [native code] }Tuohan on vanha tuttu (?) Object-funktion prototype-kentän osoittama prototyyppiolio! Näkyy kurssin symbolikuvassa ylhäällä oikealla!
function oK(o) { // omat kentät var t = Object.getOwnPropertyNames(o); for (var i=0; i<t.length; ++i) write(t[i]+": "+ o[t[i]]); write("--------"); } function kK(o) { // kaikki kentät for(var olio = o; olio !== null; olio = Object.getPrototypeOf(olio)) { write("--------"); var t = Object.getOwnPropertyNames(olio); for (var i=0; i<t.length; ++i) write(t[i]+": "+ olio[t[i]]); } }
var olio = { a: 7, getB: function () { return this.a + 1; }, setC: function (x) { this.a = x / 2 } } write(olio.a) // 7 write(olio.getB()) // 8 olio.setC(50) write(olio.a) // 25
var olio = { a: 7, get b() { return this.a + 1; }, set c(x) { this.a = x / 2 } } write(olio.a) // 7 write(olio.b) // 8 olio.c = 50 write(olio.a) // 25Tyylikästä?
// muutettuna äskeinen työkalu: var olio = { a: 7, b: 3.14, c: 1234 } // sovellus säilyy ennallaan: write(olio.a) // 7 write(olio.b) // 3.14 olio.c = 50 write(olio.a) // 7
var pankki = { tili: 1000, get saldo () {return this.tili}, set saldo (e) {this.tili+=e} } pankki.saldo=321 write(pankki.saldo) // 1321
var olio = { a:0 } Object.defineProperties(olio, { "b": { get: function () { return this.a + 1; } }, "c": { set: function (x) { this.a = x / 2; } } }); olio.c = 10 write(olio.b) // 6
Pimeä esimerkki:
function F() { this.toString = function() {return "BÖÖ!"} } var x = new F(); write(x.toString()); // BÖÖ! write(this); // [object Window] F(); write(this); // BÖÖ!Tässä siis globaalin ympäristön (this===window) toString korvattiin omalla versiolla!
Myös "tavallista" funktiota voi kutsua kuin konstrutorifunktioita:
function f(a) {return 2*a} write(f(3)) var x = new f(3) kK(x)Tulostus:
6 -------- -------- constructor: function f(a) {return 2*a} -------- toSource: function toSource() { [native code] } toString: function toString() { [native code] } toLocaleString: function toLocaleString() { [native code] } ... __proto__: null constructor: function Object() { [native code] }Ensin siis tyhjä olio, sitten f-funktion prototyyppiolio ja ketjun lopussa taas se vanha tuttu Object-funktion prototyyppiolio.
function Matkailu(matka,aika) { this.matka = matka; this.aika = aika; this.nopeus = function() {return this.matka/this.aika} } var retki = new Matkailu(150,2); write(retki.nopeus());
function Matkailu(matka,aika) { this.matka = matka; this.aika = aika; } Matkailu.prototype.nopeus = function() {return this.matka/this.aika}
function f() { function ff() { write(this===window) // true } write(this===window) // true ff() } write(this===window) // true f()
function sum(a,b) {return a+b} var plus_7 = sum.bind(null, 7); write(plus_7(9)) // 16
var x = {a:1, b:2, c:3} var y = {c:4, d:5, e:6} function muutaC(z) {this.c = z} var muutaXnC = muutaC.bind(x); var muutaYnC = muutaC.bind(y); muutaXnC(123); muutaYnC(1009); oK(x) oK(y)Tulos:
a: 1 b: 2 c: 123 -------- c: 1009 d: 5 e: 6 --------Funktio muutaC on siis "työkalu", jolla voi luoda funktion, joka osaa muuttaa minkä tahansa olion c-nimisen kentän sisällön. Ellei tuollasta kenttää oliolla ole, se luodaan:
var x = {a:1, b:2} function muutaC(z) {this.c = z} var muutaXnC = muutaC.bind(x); oK(x) muutaXnC(6543); oK(x)Tulos:
a: 1 b: 2 -------- a: 1 b: 2 c: 6543 --------Ja hölmöilläkin toki voidaan:
function muutaC(z) {this.c = z} var muutaXnC = muutaC.bind(this); write(window.c) // undefined muutaXnC(6543); write(window.c) // 6543
function sum(c, d){ return this.a + this.b + c + d; } var o = {a:1, b:2}; write( sum.call(o, 3, 4) ); // 10 (eli 1+2+3+4) write( sum.apply(o, [3,4]) ); // 10 var oo = {x:7, y:4, b:22, q:12, a:40} write( sum.call(oo, 3, 4) ); // 69 (eli 40+22+3+4) write( sum.apply(oo, [3,4]) ); // 69Niiden ensimmäinen parametri kiinnittää this-olion. Itse sum-funktiolle annetut parametrit annetaan call- ja apply-tapauksessa eri tavoin. Muuten ne toimivat samaan tapaan.