15 Desember 2021

F.prototype

Ingat ketika objek baru bisa dibuat dengan menggunakan fungsi konstruktor seperti new F().

Jika F.prototype adalah sebuah objek, maka operator new menggunakannya untuk menyetel [[Prototype]] untuk objek barunya.

Tolong dicatat:

Javascript memiliki pewarisan prototype dari awal. Itu adalah salah satu fitur utama dari bahasanya.

Tapi dimasa lalu, hal itu tidak memiliki akses langsung. Hal yang dapat diandalkan adalah properti "prototype" dari fungsi konstruktor, yang akan dijelaskan didalam bab ini. Jadi masih banyak skrip yang masih menggunakannya.

Catat bahwa F.prototype disini berarti properti biasa yang bernama "prototype" didalam F. Terdengar seperti istilah “prototype”, tapi disini kita menunjuk properti biasa dengan nama itu.

Contohnya:

let animal = {
  eats: true
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = animal;

let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal

alert( rabbit.eats ); // true

Menyetel Rabbit.prototype = animal secara literal kita mengartikan: “Ketika sebuah new Rabbit dibuat, itu akan memasukan [[Prototype]]nya ke animal”.

Hasilnya akan seperti gambar dibawah:

Dalam gambar, "prototype" adalah panah horizontal, menandakan properti regular, dan [[Prototype]] adalah panah vertikal, menandakan pewarisan rabbit dari animal.

F.prototype hanya digunakan pada new F

Properti F.prototype hanya digunakan ketika new F dipanggil, itu memasukan [[Prototype]] dari objek barunya.

Jika, setelah pembuatan, properti F.prototype berubah (F.prototype = <objek lain>), maka objek baru yang dibuat menggunakan new F akan memiliki objek lain sebagai [[Prototype]], tapi objek yang sudah ada akan menyimpan yang lama.

F.prototype bawaan, properti konstruktor

Setiap fungsi memiliki properti "prototype" bahkan jika kita tidak memberikannya.

"prototype" bawaan adalah sebuah objek dengan properti constructor yang menunjuk balik pada fungsinya sendiri.

Seperti:

function Rabbit() {}

/* default prototype
Rabbit.prototype = { constructor: Rabbit };
*/

Kita bisa periksa:

function Rabbit() {}
// secara *default*:
// Rabbit.prototype = { constructor: Rabbit }

alert( Rabbit.prototype.constructor == Rabbit ); // true

Secara teknis, jika kita tidak melakukan apapun, properti constructor akan tersedia untuk semua “rabbits” melalui [[Prototype]]:

function Rabbit() {}
// secara default:
// Rabbit.prototype = { constructor: Rabbit }

let rabbit = new Rabbit(); // mewarisi dari {constructor: Rabbit}

alert(rabbit.constructor == Rabbit); // true (from prototype)

Kita bisa menggunakan properti constructor untuk membuat objek baru menggunakan konstruktor yang sama seperti yang sudah ada.

Seperti:

function Rabbit(name) {
  this.name = name;
  alert(name);
}

let rabbit = new Rabbit("White Rabbit");

let rabbit2 = new rabbit.constructor("Black Rabbit");

Hal itu akan mudak ketika kita memiliki sebuah objek, tidak tahu konstruktor yang mana yang menggunakannya (mis. ketika datang dari library pihak ketiga), dan kita butuh membuat satu lagi dengan bentuk yang sama.

Tapi mungkin hal yang paling penting tentang "constructor" adalah…

…Javascript sendiri tidak yakin dengan nilai "constructor".

Ya, terdapat nilai untuk fungsi bawaan "prototype", tapi hanya itu. Apa yang terjadi setelahnya – semuanya bergantung pada kita.

Khususnya, jika kita mengganti seluruh prototype bawaannya, maka disana tidak akan terdapat "constructor".

Contoh:

function Rabbit() {}
Rabbit.prototype = {
  jumps: true
};

let rabbit = new Rabbit();
alert(rabbit.constructor === Rabbit); // false

Jadi, untuk menyimpan "constructor" dengan benar kita bisa memilih untuk menambahkan/menghapus properti menjadi "prototype" bawaan daripada menimpahnya dengan yang baru:

function Rabbit() {}

// Tidak menimpah Rabbit.prototype selurunya
// hanya menambahkan
Rabbit.prototype.jumps = true
// Rabbit.prototype.constructor bawaan diamankan

Atau, alternatifnya, membuat ulang properti constructor secara manual:

Rabbit.prototype = {
  jumps: true,
  constructor: Rabbit
};

// sekarang konstruktor tidak berubah, karena kita menambahkan yang baru

Ringkasan

Didalam chapter ini kita secara jelas mendeskripsikan cara untuk menyetel [[Prototype]] untuk objek yang dibuat dengan menggunakan fungsi konstruktor. Nanti kita akan melihat lebih banyak alur programming lanjutan yang akan menggunakannya.

Semuanya cukup simpel, hanya tinggal mengingat beberapa langkah untuk membuat lebih jelas:

  • Properti F.prototype (jangan keliru tentang [[Prototype]]) menyetel [[Prototype]] dari objek baru ketika new F() dipanggil.
  • Nilai dari F.prototype harusnya antara sebuah objek atau null: nilai lainnya tidak akan bekerja.
  • Properti "prototype" hanya memiliki efek spesial ketika menyetel fungsi konstruktor, dan dipanggil dengan new.

Dalam objek biasa prototype tidaklah spesial:

let user = {
  name: "John",
  prototype: "Bla-bla" // tidak ada yang spesial disini
};

Secara teknis semua fungsi memiliki F.prototype = { constructor: F }, jadi kita bisa mendapatkan konstruktor dari sebuah objek dengan mengakses properti "constructor" miliknya sendiri.

Tugas

Di kode dibawah kita membuat new Rabbit, dan mencoba memodifikasi prototypenya.

Pada awalnya kita memiliki kode ini:

function Rabbit() {}
Rabbit.prototype = {
  eats: true
};

let rabbit = new Rabbit();

alert( rabbit.eats ); // true
  1. Kita menambah satu string (ditekankan). What will alert show now?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    Rabbit.prototype = {};
    
    alert( rabbit.eats ); // ?
  2. …Dan jika kodenya seperti ini (diganti satu baris)?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    Rabbit.prototype.eats = false;
    
    alert( rabbit.eats ); // ?
  3. Dan seperti ini (diganti satu baris)?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    delete rabbit.eats;
    
    alert( rabbit.eats ); // ?
  4. varian terakhir:

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    delete Rabbit.prototype.eats;
    
    alert( rabbit.eats ); // ?

Jawaban:

  1. true.

    Memasukan ke Rabbit.prototype menyetel [[Prototype]] untuk objek baru, tapi itu tidak memberikan efek pada yang sudah ada.

  2. false.

    Objek yang dimasukan dengan menggunakan referensi. Objek dari Rabbit.prototype bukanlah di duplikasi, itu masih tetap objek tunggal yang direferensikan dari Rabbit.prototype dan dari [[Prototype]] dari rabbit.

    Jadi ketika kita mengubah kontennya melalui satu referensi, itu masih terlihat melalui yang lainnya.

  3. true.

    Semua operasi delete diterapkan langsung ke objeknya. Disini delete rabbit.eats mencoba untuk menghapus properti eats dari rabbit, tapi itu tidak memilikinya. Jadi operasinya tidak akan menghasilkan efek apapun.

  4. undefined.

    Properti eats dihapus dari prototype, itu tidak akan ada lagi.

Bayangkan, kita memiliki objek yang berubah-ubah, dibuat dengan menggunakan fungsi konstruktor – kita tidak tahu yang mana, tapi kita ingin membuat sebuah objek menggunakannya.

Bisakah kita melakukannya?

let obj2 = new obj.constructor();

Beri sebuah contoh dari menggunakan fungsi konstruktor untuk obj yang mana membiarkan kode seperti itu bekerja. Dan sebuah contoh yang mana membuat kodenya menjadi tidak bekerja semestinya.

Kita bisa menggunakan pendekatan jika kita yakin properti "constructor" memiliki nilai yang benar.

Contoh, kita tidak ingin menyentuh "prototype" bawaan, maka kode ini akan berjalan dengan semestinya:

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

let user = new User('John');
let user2 = new user.constructor('Pete');

alert( user2.name ); // Pete (bekerja!)

Kode diatas bekerja karena User.prototype.constructor == User.

Tapi jika seseorang, menimpah User.prototype dan lupa untuk membuat ulang constructor untuk User, maka akan membuat kegagalan.

Contoh:

function User(name) {
  this.name = name;
}
User.prototype = {}; // (*)

let user = new User('John');
let user2 = new user.constructor('Pete');

alert( user2.name ); // undefined

Kenapa user2.name menghasilan undefined?

Ini bagaimana new user.constructor('Pete') bekerja:

  1. Pertama, mencari constructor di user. Tidak ada.
  2. Kemudian mengikuti rantai prototipe. Prototipe user adalah User.prototype, dan juga tidak memiliki constructor (karena kita “lupa” untuk menyetelnya dengan benar!).
  3. Lebih jauh ke atas rantai, User.prototype adalah objek biasa, prototipenya adalah Object.prototype bawaan.
  4. Terakhir, untuk Object.prototype bawaan, ada Object.prototype.constructor == Object bawaan. Jadi itu digunakan.

Akhirnya, pada akhirnya, kita memiliki let user2 = new Object('Pete').

Mungkin, bukan itu yang kita inginkan. Kami ingin membuat Pengguna baru, bukan Objek baru. Itulah hasil dari konstruktor yang hilang.

(Untuk berjaga-jaga jika Anda penasaran, panggilan new Object(...) mengubah argumennya menjadi objek. Itu hal teoretis, dalam praktiknya tidak ada yang memanggil Objek baru dengan nilai, dan umumnya kami tidak’ t gunakan objek baru untuk membuat objek sama sekali).

Peta tutorial