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Åtiriciler de bu hataya düÅerler.
"this"i 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ılabilir:
let f = user.sayHi;
setTimeout(f, 1000); // 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);
// ...kullanıcının deÄeri 1 saniye içinde deÄiÅir
user = { sayHi() { alert("Another user in setTimeout!"); } };
//SetTimeout'da baÅka bir kullanıcı!
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 alı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Äildir. 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.)
bindAllEÄer bir objenin birçok metodu var ise bunu aktif olarak gerekli yerlere iletip, 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)