Soal 1Setelah const calc = cart.total;, apa isi this di dalam total saat kamu memanggil calc()?
Binding this — this Ditentukan oleh Cara Pemanggilan dan Bisa Dikunci
Method yang dimasukkan ke variabel atau dioper sebagai callback bisa kehilangan this. Membahas TypeError-nya, bind, arrow function field, dan setTimeout.
Kadang kamu mengoper method yang menghitung total keranjang ke kode lain, lalu kode itu memanggilnya belakangan. Method yang berjalan normal sebagai cart.total() bisa berhenti dengan error begitu dimasukkan ke variabel atau dioper ke setTimeout, karena this-nya berubah.
Artikel ini membahas binding this, yang ditentukan oleh cara sebuah function dipanggil, serta cara mengunci this dengan bind atau arrow function.
Error Saat Diambil dari Object — this di Dalam Method
Misalnya kamu ingin memakai cart.total() juga untuk kolom total di halaman, lalu memasukkannya ke variabel dengan const calcTotal = cart.total;. Saat calcTotal() dipanggil, kodenya berhenti sebelum sempat menghitung apa pun, padahal isi keranjangnya sama.
Value yang masuk ke this di dalam method ditentukan oleh baris yang memanggilnya, bukan oleh tempat method itu ditulis. Inilah yang disebut binding this (this diikatkan ke sebuah value). Kalau kamu membaca cart.total tanpa (), yang kamu dapat hanya function-nya; cart tidak ikut terbawa.
Pada calcTotal(), yang tidak punya apa pun di kiri titik, this di dalam class bernilai undefined.
class Cart {
constructor(items) {
this.items = items; // Subtotal tiap produk
}
total() {
return this.items.reduce((sum, price) => sum + price, 0);
}
}
const cart = new Cart([1980, 3000]);
// Panggil dengan cart di kiri titik
console.log(cart.total()); // 4980
// Masukkan ke variabel / ambil dengan destructuring
const calcTotal = cart.total;
const { total } = cart;
console.log(typeof calcTotal); // function
// calcTotal(); // TypeError: Cannot read properties of undefined (reading 'items')
// total(); // TypeError (alasannya sama)
Kolom tengah dan kanan sama-sama menampilkan TypeError: Cannot read properties of undefined (reading 'items'). Destructuring const { total } = cart; juga hanya memasukkan satu function ke variabel, jadi total() berhenti karena alasan yang sama dengan calcTotal().
Menjaga this Walau Dimasukkan ke Variabel — Method bind
Sekarang kamu ingin mengoper perhitungan total sebagai function ke kode yang menampilkan kolom total. Seperti di bagian sebelumnya, cart.total yang hanya dimasukkan ke variabel tidak membawa cart, jadi hasilnya TypeError begitu kode penerimanya memanggil function itu.
bind (method yang dimiliki setiap function, yang mengembalikan function baru dengan this dikunci ke value yang kamu berikan) ditulis cart.total.bind(cart). Function yang dikembalikan tetap memakai cart sebagai this, bahkan kalau dipanggil tanpa apa pun di kiri titik.
Yang terkunci hanya function yang dikembalikan, jadi pakailah return value itu saat memanggil atau mengopernya.
class Cart {
constructor(items) { this.items = items; } // Subtotal tiap produk
total() { return this.items.reduce((sum, price) => sum + price, 0); }
}
const cart = new Cart([1980, 3000]);
// Function yang hanya diambil dari cart mendapat this undefined saat dipanggil
const unbound = cart.total;
// unbound(); // TypeError
// bind mengembalikan function baru dengan this ditetapkan ke cart
const boundTotal = cart.total.bind(cart);
console.log(boundTotal()); // 4980
console.log(boundTotal === cart.total); // false
// Yang terkunci adalah cart itu sendiri, jadi subtotal yang ditambahkan belakangan ikut terbaca
cart.items.push(500);
console.log(boundTotal()); // 5480
total tidak menerima argument, tapi kalau kamu mengunci method yang menerima argument, argument yang kamu berikan ke return value-nya diteruskan apa adanya ke method aslinya. Yang dikunci hanya this, jadi argument-nya bisa berbeda di setiap pemanggilan.
Method yang Tetap Jalan Walau Diambil dari Instance — Arrow Function
Bayangkan method yang sama kamu oper ke beberapa tempat di halaman sebagai function yang dipanggil saat tombol diklik, misalnya tombol suka di sebuah artikel. Kalau bind harus ditulis setiap kali dan kamu lupa sekali saja, tombol itu saja yang berhenti dengan TypeError.
Arrow function field (class field yang value awalnya berupa arrow function) dibuat terpisah untuk tiap instance, setiap kali new dipanggil. Di dalam ekspresi value awal sebuah field, this adalah instance yang baru dibuat, sama seperti di constructor, dan arrow function, yang tidak punya this sendiri, memakai this tersebut.
Karena itu, kamu tidak perlu bind saat mengambil lalu memanggilnya.
class LikeButton {
likes = 0;
label = `Suka: ${this.likes}`; // this di ekspresi value awal adalah button yang sedang dibuat
// Method biasa: this ditentukan oleh baris pemanggilnya
addLikeMethod() { this.likes += 1; return this.likes; }
// Arrow function field: memakai this yang sama dengan ekspresi value awal
addLike = () => { this.likes += 1; return this.likes; };
}
const button = new LikeButton();
console.log(button.label); // Suka: 0
// Ambil lalu panggil tanpa bind
const onPress = button.addLike;
console.log(onPress()); // 1
console.log(onPress()); // 2
console.log(Object.keys(button).join(", ")); // likes, label, addLike
// Method biasa berhenti dengan TypeError kalau diambil
const onPressMethod = button.addLikeMethod;
// onPressMethod(); // TypeError
likes = 0— masuk kebuttonthisdi ekspresi value awallabel—buttonyang sedang dibuat (sama seperti di constructor)
- Tidak punya
thissendiri this.likes— membaca value milikbuttondari luar- Dipanggil sebagai
onPress()pun,thistetapbutton
this— apa yang ada di kiri titik pada baris pemanggilnyabuttonkalau dipanggil sebagaibutton.addLikeMethod()undefinedkalau diambil lalu dipanggil
addLike muncul di Object.keys(button) karena arrow function field disimpan di instance itu sendiri, sama seperti likes, sedangkan addLikeMethod tidak muncul. Artinya, kalau kamu membuat 100 instance, function addLike juga dibuat sebanyak 100.
Arrow Function di Object Literal Tidak Mendapat Object Itu sebagai this
Kalau kamu menulis const counter = { add: () => { this.likes += 1 } };, this di dalam counter.add() bukan counter. Object literal tidak menentukan this, jadi arrow function-nya memakai this dari luar. Tulis sebagai add() { ... }.
Mengoper ke Kode yang Memanggilnya Nanti — setTimeout
Katakanlah kamu ingin menampilkan pesan "Pesanan diterima" beserta nama pelanggan 100ms setelah pesanan dikonfirmasi. Kalau kamu menulis setTimeout(this.showMessage, 100) di dalam method, pesannya memang muncul 100ms kemudian, tapi nama pelanggannya menjadi undefined.
setTimeout memanggil sendiri function yang diterimanya 100ms kemudian, dan di browser, saat itu this diisi window (object yang disediakan browser, satu untuk setiap halaman). Kalau kamu membungkusnya dengan arrow function, this di () => this.showMessage() adalah this milik method di luarnya, jadi method-nya dipanggil pada instance.
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
class OrderNotice {
constructor(customerName) { this.customerName = customerName; }
showMessage() { console.log(`Pesanan diterima, ${this.customerName}`); }
// Oper method-nya apa adanya. Yang memanggilnya 100ms kemudian adalah setTimeout
notifyDirect() { setTimeout(this.showMessage, 100); }
// Bungkus dengan arrow function / kunci dengan bind sebelum dioper
notifyWithArrow() { setTimeout(() => this.showMessage(), 100); }
notifyWithBind() { setTimeout(this.showMessage.bind(this), 100); }
}
const notice = new OrderNotice("Budi");
notice.notifyDirect(); // Pesanan diterima, undefined
notice.notifyWithArrow(); // Pesanan diterima, Budi
notice.notifyWithBind(); // Pesanan diterima, Budi
await delay(300); // Di halaman ini, tunggu sampai semua output muncul
Baris atas tidak berhenti dengan TypeError karena this-nya bukan undefined seperti pada calcTotal(), melainkan window. window tidak punya customerName, jadi yang tampil adalah undefined. Tabel di bawah merangkum value this untuk setiap cara memanggil yang dibahas di artikel ini.
| Cara memanggil | Value this | Hasil di artikel ini |
|---|---|---|
| cart.total() | cart di kiri titik | Mengembalikan 4980 |
| Diambil, lalu calcTotal() | undefined | Berhenti dengan TypeError |
| Return value cart.total.bind(cart) | cart yang dioper ke bind | Mengembalikan 4980 |
| Arrow function field sebagai onPress() | button, pemilik field | Jumlah suka bertambah |
| setTimeout(this.showMessage, 100) | window (di browser) | Pesanan diterima, undefined |
| setTimeout(() => this.showMessage(), 100) | this milik method luar | Pesanan diterima, Budi |
Dibungkus dengan function pun, this Tetap Hilang
Kalau kamu membungkusnya seperti setTimeout(function () { this.showMessage(); }, 100), this di dalamnya menjadi window, jadi kodenya berhenti dengan TypeError. Console di halaman ini tidak menampilkan error itu sama sekali; Uncaught TypeError hanya muncul di developer tools browser. Bungkus dengan arrow function.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Setelah hanya menulis cart.total.bind(cart);, apa yang terjadi kalau kamu memasukkan cart.total ke variabel lalu memanggilnya?
Soal 3Saat mengoper method ke setTimeout dari dalam method lain, cara mana yang TIDAK menjaga this tetap berisi instance?