21 September 2020

Properti getter and setter

Terdapat dua jenis properti objek.

Yang pertama adalah properti data. Kita telah mengetahui bagaimana cara kerja mereka. Semua properti yang kita gunakan sampai sekarang adalah properti data.

Yang kedua adalah properti yang bisa dibilang cukup baru. Properti itu adalah properti aksesor. Mereka sebenarnya adalah fungsi untuk mendapatkan dan mengatur sebuah nilai, tapi mereka mirip seperti properti biasa pada kode eksternal.

Getter dan setter

Properti aksesor diwakili dengan method ???getter??? dan ???setter???. Didalam objek literal mereka dilambangkan dengan get dan set:

let obj = {
  get propName() {
    // getter, kode dijalankan untuk mendapat obj.propName
  },

  set propName(value) {
    // setter, kode dijalankan untuk mengatur obj.propName = value
  }
};

Getter bekerja ketika obj.propName terbaca, sedangkan setter ??? ketika variabel itu ditetapkan.

Sebagai contoh, kita memiliki sebuah objek user dengan name dan surname (nama variabel):

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

Sekarang kita ingin untuk menambahkan sebuah properti fullName, yang berisi "John Smith". kita tidak ingin untuk melakukan penyalinan terhadap informasi yang sudah ada, melainkan kita bisa menerapakan sebuah aksesor:

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

  get fullName() {
    return `${this.name} ${this.surname}`;
  }
};

alert(user.fullName); // John Smith

Dari luar, properti aksesor tampak seperti variabel pada umumnya. Itulah ide dari properti aksesor. Kita tidak memanggil user.fullName melaui fungsi, Namun kita membacanya secara biasa: properti getter berjalan di belakang layar.

Sekarang, fullName memiliki sebuah properti getter. Jika kita mencoba untuk menetapkan value lain pada user.fulName=, maka akan terjadi eror:

let user = {
  get fullName() {
    return `...`;
  }
};

user.fullName = "Test"; // Error (property has only a getter)

Mari kita perbaiki dengan menambahkan setter untuk user.fullName:

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

  get fullName() {
    return `${this.name} ${this.surname}`;
  },

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }
};

// variabel fullName dijalankan dengan value ditetapkan.
user.fullName = "Alice Cooper";

alert(user.name); // Alice
alert(user.surname); // Cooper

Alhasil, kita memiliki sebuah properti virtual fullname. Yang bisa di baca dan diatur nilainya.

Deskriptor aksesor

Deskriptor untuk properti aksesor berbeda dengan yang ada di dalam properti data.

Untuk properti aksesor, tidak ada nilai atau pengaturan dalam properti aksesor, melainkan digantikan dengan fungsi get dan set.

Yang berarti, deskriptor aksesor mungkin memiliki:

  • get ??? sebuah fungsi tanpa argument, yang bekerja ketika properti dibaca,
  • set ??? sebuah fungsi dengan satu argumen, yang dipanggil ketika properti itu ditetapkan,
  • enumerable ??? sama seperti pada properti data,
  • configurable ??? sama seperti pada properti data.

Sebagai contoh, untuk membuat sebuah aksesor fullName dengan defineProperty, kita dapat membawa sebuah deskriptor dengan get dan set:

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

Object.defineProperty(user, 'fullName', {
  get() {
    return `${this.name} ${this.surname}`;
  },

  set(value) {
    [this.name, this.surname] = value.split(" ");
  }
});

alert(user.fullName); // John Smith

for(let key in user) alert(key); // name, surname

Perlu diperhatikan bahwa sebuah properti bisa jadi adalah sebuah properti aksesor(memiliki method get/set) atau sebuah properti data(hanya memiliki sebuah nilai), namun tidak keduanya.

Jika kita mencoba untuk menyediakan get dan value pada satu deskriptor yang sama, maka akan terjadi eror.

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

  value: 2
});

getter/setter yang lebih baik

Getter/setter dapat digunakan sebagai wrapper pada properti asli(bukan aksesor) untuk mendapatkan akses kontrol lebih terkait pengoperasian dengan mereka.

Sebagai contoh, jika kita ingin untuk melarang penamaan yang terlalu singkat untuk user, kita dapat memiliki sebuah setter name dan menjaga nilainya pada properti yang terpisah _name:

let user = {
  get name() {
    return this._name;
  },

  set name(value) {
    if (value.length < 4) {
      alert("Name is too short, need at least 4 characters");
      return;
    }
    this._name = value;
  }
};

user.name = "Pete";
alert(user.name); // Pete

user.name = ""; // Name is too short...

Jadi, variabel name tersebut disimpan pada properti _name, yang aksesnya dapat melalui getter dan setter.

Secara teknis, kode eksternal bisa aja mengakses variabel nama secara langsung dengan menggunakan user._name. Tapi sudah menjadi rahasia umum bahwa properti yang diawali dengan underscore "_" adalah internal variabel yang seharusnya tidak boleh diakses dari luar.

Penggunaan kompabilitas

Salah satu kegunaan besar properti aksesor adalah mereka memperbolehkan kita untuk mengontrol properti data biasa untuk menggantinya pada suatu waktu, dengan sebuah setter dan getter, serta mengubah perilakunya.

Bayangkan kita mulai dengan implementasi objek user menggunakan properti name dan age.

function User(name, age) {
  this.name = name;
  this.age = age;
}

let john = new User("John", 25);

alert( john.age ); // 25

???Tapi cepat atau lambat, sesuatu mungkin berubah. Alih-alih menggunakan age kita mungkin memutuskan untuk menyimpan birthday, karena mungkin itu lebih tepat dan sesuai:

function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;
}

let john = new User("John", new Date(1992, 6, 1));

Sekarang apa yang akan kita lakukan terhadap kode lama yang masih menggunakan propertiage?

Kita dapat mencoba untuk mencarinya disemua tempat dan memperbaiki nya, tapi itu akan memakan waktu yang lama dan susah jika kode itu digunakan oleh banyak orang. selain itu, properti age adalah sesuatu yang bagus dimiliki oleh user, kan ?

Tetaplah menjaganya.

Menambahkan sebuah getter pada age menyelesaikan permasalahan.

function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;

  // variabel age dihitung berdasarkan tanggal sekarang dan tanggal lahirnya
  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 ); // properti birthday tersedia
alert( john.age );      // ...begitu juga dengan age

Sekarang kode yang lama bisa bekerja dan kita memiliki tambahan properti yang bagus.

Peta tutorial