Obje metodları ile setTimeout
kullanıldığında veya obje metodları iletilirken, this
'in kaybolması bilinen bir problemdir.
Aniden, this
kaybolur. Bu problem başlangıç seviyesi geliştiriciler için çok tipiktir, bazen deneyimli geliştiriceler de bu hataya düşerler.
“this”'in kaybetme
JavaScript’te this
in ne kadar kolay bir şekilde kaybolduğunu zaten biliyorsunuz. Eğer bir metod objeden farklı bir yere iletilirse this
kaybolur.
Bu setTimeout
ile nasıl olur bakalım:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000); // Hello, undefined!
Gördüğünüz gibi, çıktı “John”'u göstermedi bunun yerine undefined
döndü!
Bunun nedeni setTimeout
'un user.sayHi
fonksiyonunun objeden ayrı olmasıdır. Son satır şu şekilde yazılabilri:
let f = user.sayHi;
setTimeout(f, 1000); // lost kullanıcı kaynağı kayboldu
Tarayıcıda setTimeout
kullanımı biraz özeldir: this=window
olarak ayarlanır. ( Node.JS için this
timer objesi olur, fakat burada pek de önemli değil.) Öyleyse this.firstName
bu değeri window.firstName
'den almaya çalışır, fakat böyle birşey yok. Buna benzer durumlarda siz de göreceksiniz this
genelde undefined
olur.
Aslında yapmak istediğimiz çok basit obje metodunu çağrılan yere ( – ) iletmek istiyoruz ( burada – zamanlayıcıdır.) Bunun doğru kaynakta çağırıldığına nasıl emin olunabilir?
Çözüm 1: saklayıcı
En basit çözüm bir saklayıcı ( wrapper ) fonksiyonu kullanmaktır:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(function() {
user.sayHi(); // Hello, John!
}, 1000);
Çalışmasının nedeni user
'ı dış sözcük ortamından almasıdır, sonrasında metodu normal bir şekilde çalıştırır.
Aynısı, fakat biraz daha kısa hali:
setTimeout(() => user.sayHi(), 1000); // Hello, John!
Fena değil, fakat kod yapısal olarak biraz sorunlu görünüyor.
setTimeout
çalışmadan önce ( 1 sn ara ile çalışıyor ) user
değeri değişirse? Sonra aniden yanlış objeyi çağıracaktır.
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(() => user.sayHi(), 1000);
// ...within 1 second
user = { sayHi() { alert("Another user in setTimeout!"); } };
// Another user in setTimeout?!?
Bir sonraki çözüm içe böyle birşeyin olmasını engeller.
Çözüm 2: bağlama
Fonksiyonlar bind varsayılan fonksiyonu sağlarlar. Bu fonksiyon this
'in sabitlenmesini olanak verir.
Basitçe yazımı şu şekildedir:
// daha karmaşık yazımlarına ileride geleceğiz.
let boundFunc = func.bind(kaynak);
func.bind(kaynak)
'ın sonucu özel bir fonksiyon benzeri “egzotik obje”'dir. Fonksiyon gibi çağırılabilir ve saydam bir şekilde çağrıyı func
'a this=kaynak
olacak şekilde iletir.
Diğer bir deyişle boundFunc
aslında sabit this
'e sahip func
'dur.
Örneğin burada funcUser
çağrıyı func
fonksiyonuna this=user
olacak şekilde iletir.
let user = {
firstName: "John"
};
function func() {
alert(this.firstName);
}
let funcUser = func.bind(user);
funcUser(); // John
Burada func.bind(user)
aslında func
'un this=user
olarak “bağlanmış halidir”.
Tüm argümanlar orjinal func
'a olduğu gibi aktarılır, örneğin:
let user = {
firstName: "John"
};
function func(phrase) {
alert(phrase + ', ' + this.firstName);
}
// this'i user'a bağla.
let funcUser = func.bind(user);
funcUser("Hello"); // Hello, John ("Hello" iletildi ve this=user oldu)
Bunu obje metodu ile deneyecek olursak:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
let sayHi = user.sayHi.bind(user); // (*)
sayHi(); // Hello, John!
setTimeout(sayHi, 1000); // Hello, John!
(*)
satırında user.sayHi
metodunu aıyoruz ve user
'a bağlıyoruz. sayHi
bu durumda bağlanmış
fonksiyon oluyor. Böylece tek başına çağrılabilir veya setTimeout
içerisinde çağrılabilir. Nereden çağırıldığı çok da önemli değidlir. Kaynağı her zaman doğru olacaktır.
Gördüğünüz gibi tüm argümanlar “olduğu gibi” iletilir, sadece this
bind
tarafından sabitlenmiştir:
let user = {
firstName: "John",
say(phrase) {
alert(`${phrase}, ${this.firstName}!`);
}
};
let say = user.say.bind(user);
say("Hello"); // Hello, John ("Hello" `say` fonksiyonuna iletildi)
say("Bye"); // Bye, John ("Bye" `say` fonksiyonuna iletildi.)
bindAll
Eğer bir objenin birçok metodu var ise bunu aktik olarak gerekli yerlere iletep, bunları bir döngü içerisine alabiliriz:
for (let key in user) {
if (typeof user[key] == 'function') {
user[key] = user[key].bind(user);
}
}
Bu şekilde büyük bağlama olayları için bazı JavaScript kütüphanelerinden yardım alınabilir. Örneğin lodash ,_.bindAll(obj) fonksiyonuna sahiptir.
Özet
func.bind(kaynak, ...args)
func
fonksiyonunun “bağlanmış hali”'ni döndürür. Bu bağlanmış halde this
ve argümanlar sabitlenir.
Bind genelde obje metodlarındaki this
'in sabitlenmesi amacıyla kullanılır, sonrasında istenilen yere iletilebilir. setTimeout
örneği gibi. bind
'in modern geliştirmede kullanılmasının bir çok nedeni vardır bunlara ilerleyen konularda değineceğiz.
Yorumlar
<code>
kullanınız, birkaç satır eklemek için ise<pre>
kullanın. Eğer 10 satırdan fazla kod ekleyecekseniz plnkr kullanabilirsiniz)