[Medium] π this Binding
1. Apa itu this di JavaScript?β
Apa itu
thisdi JavaScript?
this adalah kata kunci di JavaScript yang menunjuk ke objek konteks eksekusi dari sebuah fungsi.
Nilai this bergantung pada bagaimana fungsi dipanggil, bukan di mana fungsi didefinisikan.
Aturan binding thisβ
Ada empat aturan binding untuk this di JavaScript (prioritas tertinggi ke terendah):
- new binding: fungsi dipanggil dengan
new - explicit binding:
call,apply, ataubindsecara eksplisit mengaturthis - implicit binding: dipanggil sebagai metode objek
- default binding: perilaku fallback di call site lainnya
2. Jelaskan perbedaan this di berbagai konteksβ
Jelaskan bagaimana
thisberperilaku di berbagai konteks.
1. this dalam konteks globalβ
console.log(this); // browser: window, Node.js: global
function globalFunction() {
console.log(this); // non-strict: window/global, strict: undefined
}
globalFunction();
'use strict';
function strictFunction() {
console.log(this); // undefined
}
strictFunction();
2. this dalam fungsi biasaβ
Untuk fungsi biasa, this bergantung pada call site:
function regularFunction() {
console.log(this);
}
// pemanggilan langsung
regularFunction(); // window (non-strict) atau undefined (strict)
// pemanggilan metode
const obj = {
method: regularFunction,
};
obj.method(); // obj
// call/apply/bind
const customObj = { name: 'Custom' };
regularFunction.call(customObj); // customObj
3. this dalam arrow functionβ
Arrow function tidak memiliki this sendiri.
Mereka mewarisi this dari scope leksikal luar.
const obj = {
name: 'Object',
// metode biasa
regularMethod: function () {
console.log('regularMethod this:', this); // obj
// fungsi biasa di dalam: this berubah
function innerRegular() {
console.log('innerRegular this:', this); // window/undefined
}
innerRegular();
// arrow function di dalam: this diwarisi
const innerArrow = () => {
console.log('innerArrow this:', this); // obj
};
innerArrow();
},
// arrow function sebagai properti objek
arrowMethod: () => {
console.log('arrowMethod this:', this); // window/scope leksikal global
},
};
obj.regularMethod();
obj.arrowMethod();
4. this dalam metode objekβ
const person = {
name: 'John',
age: 30,
// fungsi biasa: this -> person
greet: function () {
console.log(`Halo, saya ${this.name}`); // "Halo, saya John"
},
// singkatan metode ES6: this -> person
introduce() {
console.log(`Saya ${this.name}, umur ${this.age} tahun`);
},
// arrow function: this diwarisi dari scope luar
arrowGreet: () => {
console.log(`Halo, saya ${this.name}`); // biasanya undefined untuk name
},
};
person.greet();
person.introduce();
person.arrowGreet();
5. this dalam fungsi konstruktorβ
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function () {
console.log(`Halo, saya ${this.name}`);
};
}
const john = new Person('John', 30);
john.greet();
console.log(john.name); // "John"
6. this dalam classβ
class Person {
constructor(name) {
this.name = name;
}
// metode biasa: this -> instance
greet() {
console.log(`Halo, saya ${this.name}`);
}
// arrow function class field: this terikat permanen ke instance
arrowGreet = () => {
console.log(`Hai, saya ${this.name}`);
};
}
const john = new Person('John');
john.greet(); // "Halo, saya John"
// ekstraksi metode kehilangan this
const greet = john.greet;
greet(); // error di strict mode
// arrow field mempertahankan this
const arrowGreet = john.arrowGreet;
arrowGreet(); // "Hai, saya John"
3. Kuis: Apa yang akan dicetak?β
Kuis: apa yang akan dicetak oleh kode berikut?
Pertanyaan 1: metode objek vs arrow functionβ
const obj = {
name: 'Object',
regularFunc: function () {
console.log('A:', this.name);
},
arrowFunc: () => {
console.log('B:', this.name);
},
};
obj.regularFunc();
obj.arrowFunc();
Klik untuk melihat jawaban
// A: Object
// B: undefined
Penjelasan:
regularFuncdipanggil sebagaiobj.regularFunc(), jadithisadalahobjarrowFunctidak punyathissendiri; mewarisithisleksikal luar/global
Pertanyaan 2: meneruskan fungsi sebagai argumenβ
const person = {
name: 'John',
greet: function () {
console.log(`Halo, ${this.name}`);
},
};
person.greet(); // 1
const greet = person.greet;
greet(); // 2
setTimeout(person.greet, 1000); // 3
Klik untuk melihat jawaban
// 1: "Halo, John"
// 2: "Halo, undefined" atau error di strict mode
// 3: "Halo, undefined" atau error di strict mode
Penjelasan:
person.greet()-> implicit binding,thisadalahperson- Fungsi yang diekstrak ->
thishilang - Callback diteruskan ke
setTimeout->thisbukanperson
Pertanyaan 3: fungsi bersarangβ
const obj = {
name: 'Outer',
method: function () {
console.log('A:', this.name);
function inner() {
console.log('B:', this.name);
}
inner();
const arrow = () => {
console.log('C:', this.name);
};
arrow();
},
};
obj.method();
Klik untuk melihat jawaban
// A: Outer
// B: undefined
// C: Outer
Penjelasan:
A:methoddipanggil olehobjB:inneradalah pemanggilan langsung biasaC: arrow function mewarisithisdarimethodluar
Pertanyaan 4: setTimeout dan arrow functionβ
const obj = {
name: 'Object',
method1: function () {
setTimeout(function () {
console.log('A:', this.name);
}, 100);
},
method2: function () {
setTimeout(() => {
console.log('B:', this.name);
}, 100);
},
};
obj.method1();
obj.method2();
Klik untuk melihat jawaban
// A: undefined
// B: Object
Penjelasan:
A: callback biasa disetTimeoutkehilangan konteks metodeB: callback arrow mewarisithisdarimethod2
Pertanyaan 5: binding this yang kompleksβ
const obj1 = {
name: 'obj1',
getThis: function () {
return this;
},
};
const obj2 = {
name: 'obj2',
};
console.log('A:', obj1.getThis().name);
const getThis = obj1.getThis;
console.log('B:', getThis() === window); // asumsi browser
obj2.getThis = obj1.getThis;
console.log('C:', obj2.getThis().name);
const boundGetThis = obj1.getThis.bind(obj2);
console.log('D:', boundGetThis().name);
Klik untuk melihat jawaban
// A: obj1
// B: true
// C: obj2
// D: obj2
Penjelasan:
A: dipanggil dariobj1B: pemanggilan langsung menggunakan default binding (window di browser non-strict)C: dipanggil dariobj2D: secara eksplisit di-bind denganbind(obj2)
Pertanyaan 6: konstruktor dan prototypeβ
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
console.log(`Halo, saya ${this.name}`);
};
Person.prototype.delayedGreet = function () {
setTimeout(function () {
console.log('A:', this.name);
}, 100);
};
Person.prototype.arrowDelayedGreet = function () {
setTimeout(() => {
console.log('B:', this.name);
}, 100);
};
const john = new Person('John');
john.delayedGreet();
john.arrowDelayedGreet();
Klik untuk melihat jawaban
// A: undefined
// B: John
Penjelasan:
A: callback timeout biasa menggunakan binding default/globalB: callback timeout arrow mewarisithisinstance
Pertanyaan 7: variabel global vs properti objekβ
var name = 'jjjj';
var obj = {
a: function () {
name = 'john';
console.log(this.name);
},
};
obj.a();
Klik untuk melihat jawaban
// undefined
Penjelasan:
Kuncinya adalah perbedaan antara variabel global dan properti objek.
thisdiobj.a()menunjuk keobjname = 'john'(tanpa deklarasi) memperbarui variabel globalthis.namemembacaobj.nameobjtidak punya propertiname, jadi hasilnyaundefined
Alur eksekusi:
// awal
window.name = 'jjjj';
obj = {
a: function () {
/* ... */
},
// obj tidak punya properti name
};
obj.a();
β
window.name = 'john'; // nilai global berubah
this.name; // sama dengan obj.name
obj.name; // undefined
Jika Anda ingin 'john', tugaskan via this.name = 'john'.
var obj = {
a: function () {
this.name = 'john';
console.log(this.name); // 'john'
},
};
obj.a();
console.log(obj.name); // 'john'
Pertanyaan 8: jebakan variabel global (lanjutan)β
var name = 'global';
var obj = {
name: 'object',
a: function () {
name = 'modified';
console.log('1:', name); // variabel global
console.log('2:', this.name); // properti objek
},
};
obj.a();
console.log('3:', name); // variabel global
console.log('4:', obj.name); // properti objek
Klik untuk melihat jawaban
// 1: modified
// 2: object
// 3: modified
// 4: object
Poin kunci:
nametanpathis.-> variabel globalthis.name-> properti objek- Keduanya adalah nilai yang berbeda
4. Bagaimana cara mempertahankan this di callback?β
Bagaimana cara mempertahankan
thisdi dalam fungsi callback?
Metode 1: arrow functionβ
const obj = {
name: 'Object',
method: function () {
setTimeout(() => {
console.log(this.name); // "Object"
}, 1000);
},
};
obj.method();
Metode 2: bind()β
const obj = {
name: 'Object',
method: function () {
setTimeout(
function () {
console.log(this.name); // "Object"
}.bind(this),
1000
);
},
};
obj.method();
Metode 3: simpan this di variabel (pola legacy)β
const obj = {
name: 'Object',
method: function () {
const self = this;
setTimeout(function () {
console.log(self.name); // "Object"
}, 1000);
},
};
obj.method();
Metode 4: call() / apply()β
function greet() {
console.log(`Halo, saya ${this.name}`);
}
const person1 = { name: 'John' };
const person2 = { name: 'Jane' };
greet.call(person1); // "Halo, saya John"
greet.apply(person2); // "Halo, saya Jane"
5. Jebakan this yang umumβ
Jebakan
thisyang umum
Jebakan 1: mengekstrak metode objekβ
const obj = {
name: 'Object',
greet: function () {
console.log(this.name);
},
};
obj.greet(); // β
"Object"
const greet = obj.greet;
greet(); // β this hilang
const boundGreet = obj.greet.bind(obj);
boundGreet(); // β
"Object"
Jebakan 2: this di event listenerβ
const button = document.querySelector('button');
const obj = {
name: 'Object',
// β arrow function di sini tidak bind ke button
handleClick1: () => {
console.log(this); // window/leksikal global
},
// β
fungsi biasa di listener mendapat event target sebagai this
handleClick2: function () {
console.log(this); // elemen button
},
// β
gunakan pembungkus arrow ketika Anda memerlukan this objek di dalam callback
handleClick3: function () {
button.addEventListener('click', () => {
console.log(this.name); // "Object"
});
},
};
Jebakan 3: callback di metode arrayβ
const obj = {
name: 'Object',
items: [1, 2, 3],
// β callback biasa kehilangan this
processItems1: function () {
this.items.forEach(function (item) {
console.log(this.name, item);
});
},
// β
callback arrow mempertahankan this leksikal
processItems2: function () {
this.items.forEach((item) => {
console.log(this.name, item);
});
},
// β
gunakan thisArg
processItems3: function () {
this.items.forEach(function (item) {
console.log(this.name, item);
}, this);
},
};
6. Ringkasan aturan binding thisβ
Ringkasan aturan binding
this
Prioritas (tinggi -> rendah)β
// 1. new binding (tertinggi)
function Person(name) {
this.name = name;
}
const john = new Person('John');
console.log(john.name); // "John"
// 2. explicit binding (call/apply/bind)
function greet() {
console.log(this.name);
}
const obj = { name: 'Object' };
greet.call(obj); // "Object"
// 3. implicit binding (metode objek)
const obj2 = {
name: 'Object2',
greet: greet,
};
obj2.greet(); // "Object2"
// 4. default binding (terendah)
greet(); // undefined (strict) atau nama global (non-strict)
Function vs Arrow Functionβ
| Fitur | Function | Arrow Function |
|---|---|---|
Punya this sendiri | β Ya | β Tidak |
this ditentukan oleh | Call site | Scope definisi leksikal |
call/apply/bind bisa mengubah this | β Ya | β Tidak |
| Bisa jadi konstruktor | β Ya | β Tidak |
Punya arguments | β Ya | β Tidak |
| Terbaik untuk | Metode objek, konstruktor | Callback, warisan this luar |
Frasa untuk diingatβ
"Arrow mewarisi, function bergantung pada pemanggilan."
- Arrow function:
thisdiwarisi dari scope leksikal luar- Fungsi biasa:
thisditentukan saat runtime oleh call site
7. Best practiceβ
Best practice
β Direkomendasikanβ
// 1. Gunakan fungsi biasa atau singkatan metode untuk metode objek
const obj = {
name: 'Object',
// β
baik
greet: function () {
console.log(this.name);
},
// β
baik
introduce() {
console.log(this.name);
},
};
// 2. Gunakan arrow function untuk callback yang harus mempertahankan this luar
class Component {
constructor() {
this.name = 'Component';
}
mount() {
setTimeout(() => {
console.log(this.name);
}, 1000);
}
}
// 3. Gunakan fungsi biasa ketika this dinamis diperlukan
const button = {
label: 'Klik saya',
handleClick: function () {
console.log(this); // event target / objek penerima
},
};
β Tidak direkomendasikanβ
// 1. Hindari arrow function sebagai metode objek
const obj = {
name: 'Object',
greet: () => {
console.log(this.name); // undefined dalam kebanyakan kasus
},
};
// 2. Hindari arrow function sebagai konstruktor
const Person = (name) => {
this.name = name; // salah
};
// 3. Hindari arrow ketika Anda memerlukan objek arguments
const sum = () => {
console.log(arguments); // ReferenceError
};