15 Desember 2021

Pengecekan kelas: "instanceof"

Operator instanceof memungkinkan kita untuk memeriksa apakah suatu object milik class tertentu. instanceof juga memperhatikan inheritance.

Pengecekan seperti itu mungkin diperlukan dalam beberapa kasus. Di sini kita akan menggunakannya untuk membangun fungsi polymorphic, yang memperlakukan argumen secara berbeda bergantung pada tipenya.

Operator instanceof

Sintaksnya adalah:

obj instanceof Class

Akan mengembalikan true jika obj dimiliki oleh Class atau kelas turunannya.

Misalnya:

class Rabbit {}
let rabbit = new Rabbit();

// apakah ini merupakan object dari kelas Rabbit?
alert( rabbit instanceof Rabbit ); // true

Itu juga bekerja dengan fungsi constructor:

// dari pada class
function Rabbit() {}

alert( new Rabbit() instanceof Rabbit ); // true

???Dan kelas bawaan seperti Array:

let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true

Harap dicatat bahwa arr juga termasuk dalam kelas Object. Itu karena Array secara prototipikal mewarisi dariObject.

Normalnya, instanceof memeriksa rantai prototype untuk pemeriksaan. Kita juga dapat menyetel custom logic dalam static method Symbol.hasInstance.

Algoritma obj instanceof Class bekerja kurang lebih sebgai berikut:

  1. Jika terdapat static method Symbol.hasInstance, maka panggil saja: Class[Symbol.hasInstance](obj). Itu akan mengembalikan true atau false, selesai. Begitulah cara kita bisa menyesuaikan perilaku dari instanceof.

    Sebagai contoh:

    // menyiapkan instanceOf yang berasumsi
    // apapun yang memiliki properti canEat adalah binatang
    class Animal {
      static [Symbol.hasInstance](obj) {
        if (obj.canEat) return true;
      }
    }
    
    let obj = { canEat: true };
    
    alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) dipanggil
  2. Kebanyakan kelas tidak memiliki Symbol.hasInstance. Dalam kasus ini, logika standar digunakan: obj instanceOf Class Memeriksa apakah Class.prototype sama dengan salah satu prototype dalam rantai prototypeobj .

    Dengan kata lain, bandingkan satu sama lain:

    obj.__proto__ === Class.prototype?
    obj.__proto__.__proto__ === Class.prototype?
    obj.__proto__.__proto__.__proto__ === Class.prototype?
    ...
    // jika jawabannya true, return true
    // jika tidak, jika kita mencapai ujung rantai, return false

    Pada contoh diatas rabbit.__proto__ === Rabbit.prototype, sehingga akan memberikan jawaban segera.

    Dalam kasus inheritance, kesamaan akan berada pada langkah kedua:

    class Animal {}
    class Rabbit extends Animal {}
    
    let rabbit = new Rabbit();
    alert(rabbit instanceof Animal); // true
    
    // rabbit.__proto__ === Rabbit.prototype
    // rabbit.__proto__.__proto__ === Animal.prototype (match!)

Berikut ilustrasi tentang perbandingan rabbit instanceof Animal dengan Animal.prototype:

Omong-omong, terdapat juga method objA.isPrototypeOf(objB), yang mengembalikan true jika objA berada di suatu tempat dalam rantai prototypes untuk objB. Jadi pengujian obj instanceof Class bisa dirumuskan sebagai Class.prototype.isPrototypeOf(obj).

Ini lucu, tetapi constructor Class itu sendiri tidak ikut dalam pemeriksaan! Hanya rangkaian dari prototype dan Class.prototype yang penting.

Itu bisa menimbulkan konsekuensi yang menarik ketika properti prototype diubah setelah objek dibuat.

Seperti:

function Rabbit() {}
let rabbit = new Rabbit();

// mengubah prototype
Rabbit.prototype = {};

// ...bukan kelinci lagi!
alert( rabbit instanceof Rabbit ); // false

Bonus: Object.prototype.toString untuk tipe

Kita tahu bahwa plain object akan diubah menjadi string sebagai [object Object]:

let obj = {};

alert(obj); // [object Object]
alert(obj.toString()); // sama

Itulah implementasi dari toString. Tetapi ada fitur tersembunyi yang membuat toString menjadi lebih powerful dari itu. Kita bisa menggunakannya sebagai perluasan dari typeof dan alternatif untuk instanceof.

Terdengar aneh? Tentu. Mari kita cari tahu.

Dengan spesifikasi, toString bawaan dapat diekstrak dari object dan dijalankan dalam konteks nilai lainnya. Dan hasilnya tergantung pada nilai tersebut.

  • Untuk angka, akan menjadi [object Number]
  • Untuk boolean, akan menjadi [object Boolean]
  • Untuk null: [object Null]
  • Untuk undefined: [object Undefined]
  • Untuk array: [object Array]
  • ???dll (dapat disesuaikan).

Mari kita tunjukkan:

// copy toString method kedalam sebuah variabel
let objectToString = Object.prototype.toString;

// tipe apa ini?
let arr = [];

alert( objectToString.call(arr) ); // [object Array]

Disini kita gunakan call seperti dijelaskan dalam bab Decorators dan forwarding, call/apply untuk menjalankan fungsi objectToString dalam konteks this=arr.

Secara internal, algoritme toString memeriksa this dan mengembalikan hasil yang sesuai. Contoh lainnya:

let s = Object.prototype.toString;

alert( s.call(123) ); // [object Number]
alert( s.call(null) ); // [object Null]
alert( s.call(alert) ); // [object Function]

Symbol.toStringTag

Perilaku object toString dapat disesuaikan dengan menggunakan properti objek khusus Symbol.toStringTag.

Sebagai contoh:

let user = {
  [Symbol.toStringTag]: "User"
};

alert( {}.toString.call(user) ); // [object User]

Untuk sebagian besar objek yang spesifik pada environment, terdapat properti seperti itu. Berikut beberapa contoh untuk browser yang spesifik:

// toStringTag untuk objek dan kelas yang spesifik pada environtment:
alert( window[Symbol.toStringTag]); // Window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest

alert( {}.toString.call(window) ); // [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]

Dapat dilihat, hasilnya persis Symbol.toStringTag (jika ada), digabungkan ke dalam [object ...].

Pada akhirnya kami memiliki ???typeof on steroids??? yang tidak hanya akan berfungsi untuk tipe data primitif, tetapi juga untuk objek bawaan dan bahkan dapat disesuaikan.

Kita dapat menggunakan {}.toString.call daripada instanceof untuk objek bawaan ketika ingin mendapatkan tipe sebagai string daripada hanya untuk diperiksa.

Ringkasan

Mari kita rangkum metode pengecekan tipe yang kita ketahui:

bekerja pada mengembalikan
typeof primitif string
{}.toString primitif, objek bawaan, objek dengan Symbol.toStringTag string
instanceof objek true/false

Dapat kita lihat, {}.toString secara teknis ???lebih advanced??? typeof.

Dan operator instanceof akan lebih berguna ketika kita bekerja dengan hirearki kelas dan ingin memeriksa kelas yang memperhatikan inheritance.

Tugas

pentingnya: 5

Pada kode dibawah, kenapa instanceof mengembalikan true? Dengan jelas kita dapat melihat a tidak dibuat oleh B().

function A() {}
function B() {}

A.prototype = B.prototype = {};

let a = new A();

alert( a instanceof B ); // true

Yah, memang terlihat aneh.

Tetapi instanceof tidak peduli dengan fungsinya, melainkan tentang prototype, yang cocok dengan rantai prototipe.

Dan di sini a.__proto__ == B.prototype, jadi instanceof mengembalikan true.

Jadi, dengan menggunakan logika instanceof, prototype sebenarnya mendefinisikan tipe, bukan fungsi konstruktor.

Peta tutorial