Dasar this

Aturan call-site yang menentukan nilai this dan kebingungan klasiknya.

Daftar isi

this adalah kata kunci yang nilainya ditentukan saat sebuah fungsi dipanggil, bukan saat fungsinya ditulis. Penentuannya mengikuti bentuk pemanggilan pada call-site, yaitu baris tempat fungsi itu dipanggil. Topik ini menata aturan call-site dari yang paling sering ditemui, membahas panggilan lepas pada mode biasa dan mode strict, serta memperkenalkan call, apply, dan bind sebagai alat pengatur this yang eksplisit. Bagian penutupnya membahas jebakan klasik ketika sebuah metode dipindah keluar dari object-nya. Seluruh contoh berupa berkas JavaScript utuh yang dijalankan dengan Node.js.

Bentuk pemanggilan yang menentukan this#

Aturan utamanya menyasar pemanggilan metode. Ketika fungsi dipanggil lewat sebuah object, misalnya buku.perkenalkan(), nilai this di dalam fungsinya adalah object yang berada di sebelah kiri titik, yaitu buku. Aturan yang sama bekerja pada object bersarang, anggota.kartu.info() menetapkan this ke kartu, bukan ke anggota, karena metode itu dipanggil langsung dari object tempat metode tersebut melekat. Nilai this dibentuk oleh bentuk pemanggilan pada call-site, bukan oleh lokasi penulisan fungsinya. Fungsi yang sama bisa menghasilkan this berbeda bila dipanggil dari tempat berbeda.

Berkas berikut memuat dua object yang saling bebas, yaitu buku dan anggota, dengan metode yang membaca datanya lewat this. Metode info berada di dalam kartu yang bersarang, sehingga cocok untuk menguji object mana yang benar-benar menjadi this. Jalankan berkasnya dan perhatikan kedua baris keluarannya.

js aturan-this.js
// Nilai this mengikuti object di sebelah kiri titik saat pemanggilan.

const buku = {
  judul: "Kamus Bahasa Indonesia",
  perkenalkan() {
    return "Judul buku: " + this.judul;
  }
};

const anggota = {
  nama: "Citra",
  kartu: {
    nomor: "P-1024",
    info() {
      return "Kartu " + this.nomor + " atas nama " + anggota.nama;
    }
  }
};

console.log(buku.perkenalkan());
console.log(anggota.kartu.info());
text Keluaran
Judul buku: Kamus Bahasa Indonesia
Kartu P-1024 atas nama Citra

Kedua metode berhasil membaca datanya karena dipanggil lewat object yang memuatnya. Pada buku.perkenalkan(), this berisi buku sehingga this.judul menemukan judulnya. Pada anggota.kartu.info(), this berisi kartu sehingga this.nomor menghasilkan nomor kartu, sedangkan nama anggota diakses langsung lewat variabel anggota karena anggota bukan this pada call-site tersebut.

Panggilan lepas: mode biasa dan mode strict#

Fungsi yang dipanggil tanpa object di depannya, misalnya uji() tanpa titik, disebut panggilan lepas. Pada mode biasa, JavaScript mengganti this yang seharusnya kosong dengan globalThis, yaitu object global yang tersedia di semua lingkungan, termasuk Node.js dan browser. Pada strict mode, yaitu mode bahasa yang lebih ketat yang diaktifkan lewat "use strict" atau otomatis di modul ES, penggantian itu tidak terjadi dan this bernilai undefined. Berkas berikut menampilkan keduanya dalam satu berkas.

js panggilan-lepas.js
// Panggilan lepas, nilai this bergantung pada mode bahasa.

function ujiLonggar() {
  return this === globalThis;
}

function ujiStrict() {
  "use strict";
  return this;
}

console.log(ujiLonggar());
console.log(ujiStrict());
text Keluaran
true
undefined

Fungsi ujiLonggar berjalan pada mode biasa sehingga this-nya adalah globalThis dan perbandingannya menghasilkan true. Fungsi ujiStrict mengaktifkan strict mode untuk tubuhnya sendiri, sehingga pemanggilan lepasnya menghasilkan undefined tanpa penggantian apa pun. Konsekuensinya praktis, this.nama pada fungsi strict yang dipanggil lepas memunculkan error TypeError karena membaca properti dari undefined, sedangkan pada mode biasa nilai itu hanya undefined tanpa error.

call, apply, dan bind#

Ketika nilai this perlu ditetapkan secara eksplisit, JavaScript menyediakan tiga metode yang melekat pada setiap fungsi. Ketiganya menerima this sebagai argumen pertama, sehingga keputusan soal this berpindah dari call-site ke tangan penulis kode. Metode call menjalankan fungsi seketika dengan this sebagai argumen pertama dan argumen lainnya ditulis berurutan. Metode apply bekerja seperti call, tetapi argumen fungsinya dikumpulkan dalam satu array. Metode bind tidak menjalankan apa pun, melainkan mengembalikan fungsi baru yang this-nya terikat permanen pada nilai yang diberikan.

js aturan-eksplisit.js
// call, apply, dan bind mengatur this secara eksplisit.

function sapa(pesan) {
  return pesan + ", " + this.nama;
}

const pesertaA = { nama: "Dewi" };
const pesertaB = { nama: "Ali" };

console.log(sapa.call(pesertaA, "Selamat datang"));
console.log(sapa.apply(pesertaA, ["Selamat malam"]));

const sapaAli = sapa.bind(pesertaB, "Selamat pagi");
console.log(sapaAli());
text Keluaran
Selamat datang, Dewi
Selamat malam, Dewi
Selamat pagi, Ali

Dua pemanggilan pertama menghasilkan sapaan untuk Dewi karena call dan apply sama-sama menetapkan this ke pesertaA, dengan perbedaan hanya pada cara argumennya ditulis. Metode bind menghasilkan sapaAli, fungsi baru yang siap dipanggil berulang kali tanpa khawatir this-nya berpindah, dan sapaannya menyasar pesertaB. Ketiganya tidak mengubah fungsi aslinya, sapa tetap bebas dipanggil dengan this lain pada call-site berikutnya.

Kebingungan klasik: metode yang kehilangan this#

Jebakan yang paling sering ditemui terjadi ketika sebuah metode dipindah keluar dari object-nya, misalnya disimpan ke variabel atau dioper sebagai callback. Penyimpanan hanya menyalin referensi fungsinya, sedangkan hubungan metode dengan object miliknya tidak ikut tersalin. Nama variabel penampungnya tidak berpengaruh apa pun, karena yang menentukan tetap ada atau tidaknya object di depan titik saat pemanggilan. Pada call-site yang baru, pemanggilan menjadi panggilan lepas, sehingga this-nya tidak lagi menunjuk object semula.

js kehilangan-this.js
// Metode yang dipindah ke variabel kehilangan this-nya.

const penghitung = {
  jumlah: 0,
  naik() {
    this.jumlah += 1;
    return this.jumlah;
  }
};

console.log(penghitung.naik());
console.log(penghitung.naik());

const naikLepas = penghitung.naik;
console.log(naikLepas());
console.log(penghitung.jumlah);

const naikTerasosiasi = penghitung.naik.bind(penghitung);
console.log(naikTerasosiasi());
console.log(penghitung.jumlah);
text Keluaran
1
2
NaN
2
3
3

Dua pemanggilan pertama berjalan normal karena call-site-nya masih penghitung.naik(), sehingga this menunjuk object itu dan jumlahnya naik menjadi 2. Pemanggilan lewat naikLepas() kehilangan this, JavaScript menggantinya dengan globalThis, sehingga this.jumlah += 1 menghitung undefined + 1 dan hasilnya NaN, sedangkan penghitung.jumlah tetap 2. Perbaikannya memakai bind. Pemanggilan naikTerasosiasi() menjalankan fungsi yang sama dengan this terikat pada penghitung, sehingga jumlahnya naik menjadi 3 dan tersimpan di tempat yang benar.

Ringkasan#

  • this ditentukan oleh bentuk pemanggilan pada call-site, bukan oleh tempat penulisan fungsinya.
  • Pada pemanggilan metode, this adalah object di sebelah kiri titik, misalnya buku pada buku.perkenalkan().
  • Panggilan lepas menghasilkan globalThis pada mode biasa dan undefined pada strict mode, termasuk di dalam modul ES.
  • call dan apply menjalankan fungsi dengan this pilihan dan argumen yang ditulis berurutan atau dalam array, sedangkan bind mengembalikan fungsi baru dengan this terikat permanen.

Referensi#