Objektin ominaisuuksia on kahdenlaisia.

Ensimmäinen tyyppi on dataominaisuudet. Tiedämme jo, miten niiden kanssa työskennellään. Kaikki tähän asti käyttämämme ominaisuudet ovat olleet dataominaisuuksia.

Toisenlaiset ominaisuudet ovat jotain uutta. Se on accessor-ominaisuudet. Ne ovat pohjimmiltaan funktioita, jotka suoritetaan saadessaan ja asettaessaan arvon, mutta näyttävät ulkoisen koodin silmissä tavallisilta ominaisuuksilta.

Getterit ja setterit

Accessor-ominaisuuksia edustavat ”getter”- ja ”setter”-metodit. Objektilitteraalissa niitä merkitään get ja set:

let obj = { get propName() { // getter, the code executed on getting obj.propName }, set propName(value) { // setter, the code executed on setting obj.propName = value }};

Getter toimii, kun obj.propName luetaan, setter – kun se annetaan.

Esimerkiksi meillä on user-olio, jolla on name ja surname:

let user = { name: "John", surname: "Smith"};

Nyt haluamme lisätä fullName-ominaisuuden, jonka pitäisi olla "John Smith". Emme tietenkään halua kopioida olemassa olevaa tietoa, joten voimme toteuttaa sen accessorina:

let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }};alert(user.fullName); // John Smith

Ulkoapäin katsottuna accessor-ominaisuus näyttää tavalliselta. Se on accessor-ominaisuuksien idea. Emme kutsu user.fullName funktioksi, vaan luemme sen normaalisti: getteri toimii kulissien takana.

Tänään fullName:llä on vain getteri. Jos yritämme osoittaa user.fullName=, tulee virhe:

let user = { get fullName() { return `...`; }};user.fullName = "Test"; // Error (property has only a getter)

let user = { get fullName() { return `...`; }};user.fullName = "Test"; // Error (property has only a getter)
let user = { get fullName() { return `...`; }};user.fullName = "Test"; // Error (property has only a getter)

Korjataan asia lisäämälläuser.fullName:lle setter:

let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }, set fullName(value) { = value.split(" "); }};// set fullName is executed with the given value.user.fullName = "Alice Cooper";alert(user.name); // Alicealert(user.surname); // Cooper

Tuloksena meillä on ”virtuaalinen” ominaisuus fullName. Se on luettavissa ja kirjoitettavissa.

Käyttäjäominaisuuksien kuvaajat

Käyttäjäominaisuuksien kuvaajat eroavat dataominaisuuksien kuvaajista.

Käyttäjäominaisuuksille ei ole value tai writable, vaan niiden sijaan on get ja set funktiot.

Tämä tarkoittaa, että accessor-kuvaajalla voi olla:

  • get – funktio ilman argumentteja, joka toimii, kun ominaisuutta luetaan,
  • set – funktio, jolla on yksi argumentti, jota kutsutaan, kun ominaisuutta asetetaan,
  • enumerable – sama kuin data-ominaisuuksille,
  • configurable – sama kuin data-ominaisuuksille.

Esimerkiksi luodaksemme accessorin fullName, jolla on defineProperty, voimme antaa kuvaajan, jolla on get ja set:

let user = { name: "John", surname: "Smith"};Object.defineProperty(user, 'fullName', { get() { return `${this.name} ${this.surname}`; }, set(value) { = value.split(" "); }});alert(user.fullName); // John Smithfor(let key in user) alert(key); // name, surname

Huomaa, että ominaisuus voi olla joko accessor (jolla on get/set-metodit) tai dataominaisuus (jolla on value), ei molempia.

Jos yritämme antaa sekä get että value samassa kuvaajassa, tulee virhe:

// Error: Invalid property descriptor.Object.defineProperty({}, 'prop', { get() { return 1 }, value: 2});

Smarter getterit/setterit

Gettereitä/settereitä voidaan käyttää kääreinä ”oikeiden” ominaisuuksien arvojen päällä, jotta niillä tehtäviä operaatioita voidaan hallita paremmin.

Teknisesti ulkoinen koodi voi käyttää nimeä suoraan käyttämällä user._name. Mutta on olemassa laajalti tunnettu konventio, jonka mukaan alleviivauksella "_" alkavat ominaisuudet ovat sisäisiä, eikä niihin saisi koskea objektin ulkopuolelta.

Yhteensopivuus

Yksi accessoreiden hienoimmista käyttökohteista on se, että ne mahdollistavat ”tavallisen” datan ominaisuuden hallinnan haltuunoton milloin tahansa korvaamalla se getterillä ja setterillä ja muokkaamalla sen käyttäytymistä.

Kuvitellaan, että alkaisimme toteuttaa käyttäjäobjekteja käyttäen dataominaisuuksia name ja age:

function User(name, age) { this.name = name; this.age = age;}let john = new User("John", 25);alert( john.age ); // 25

…Mutta ennemmin tai myöhemmin asiat voivat muuttua. age:n sijaan saatamme päättää tallentaa birthday:n, koska se on tarkempi ja kätevämpi:

function User(name, birthday) { this.name = name; this.birthday = birthday;}let john = new User("John", new Date(1992, 6, 1));

Mitä nyt tehdään vanhalle koodille, joka edelleen käyttää age-ominaisuutta?

Voisimme pyrkiä löytämään kaikki tällaiset paikat ja korjaamaan ne, mutta se vie aikaa, ja se voi olla hankalaa, jos kyseistä koodia käyttävät monet muutkin. Ja sitä paitsi age on mukava asia user:ssä, eikö?

Pidetään se.

Getterin lisääminen age:lle ratkaisee ongelman:

function User(name, birthday) { this.name = name; this.birthday = birthday; // age is calculated from the current date and birthday Object.defineProperty(this, "age", { get() { let todayYear = new Date().getFullYear(); return todayYear - this.birthday.getFullYear(); } });}let john = new User("John", new Date(1992, 6, 1));alert( john.birthday ); // birthday is availablealert( john.age ); // ...as well as the age

Nyt vanha koodi toimii myös ja meillä on kiva lisäominaisuus.

Vastaa

Sähköpostiosoitettasi ei julkaista.