Mert Özen Her Satırda Daha İleri
Frontend JavaScript Orta — #04

JavaScript this, call, apply ve bind: this Değeri Neye Göre Belirlenir?

Mert Özen 3 Kas 2026 8 dk 4 görüntülenme
JavaScript this, call, apply ve bind: this Değeri Neye Göre Belirlenir?

this'in çağrı biçimine göre nasıl belirlendiğini, method kaybı tuzağını, arrow function'ların this'ini, call/apply/bind ile this'i ayarlamayı, event handler'lardaki this'i ve bind ile partial application'ı öğreniyoruz.

Sürüm notu: Bu yazıdaki kodlar ES2026 ve güncel tarayıcılar (Chrome, Edge, Firefox, Safari) içindir.

Önce Tahmin Et

Bir object, bir method ve aynı method'un bir değişkene kopyalanmış hâli:

const user = {
  firstName: "Ada",
  greet() {
    return `Hello, I'm ${this.firstName}`;
  }
};

const greet = user.greet;
console.log(user.greet());
console.log(greet());

İki satır da aynı fonksiyonu çağırıyor. Bu kod konsola ne yazdırır? Okumaya devam etmeden önce bir tahmin yap. Cevabı this'in nasıl belirlendiğini gördükten hemen sonra vereceğiz.

this Bir Soru Sorar: Beni Kim Çağırdı?

Türkçedeki "ben" kelimesini düşün. Kâğıtta tek başına yazdığında kimi kastettiği belli değildir; anlamı, cümleyi kimin söylediğine göre değişir. this de böyledir. Daha önce gördüğümüz scope chain'de değişkenler, fonksiyonun yazıldığı yere göre bulunuyordu. this bu kurala uymaz: değeri, fonksiyon çağrıldığı anda, çağrının biçimine göre belirlenir.

Engine bir fonksiyonu çalıştırmadan hemen önce kendine şunu sorar: "Bu fonksiyon nasıl çağrıldı?" Cevaba göre this'e bir değer verir. Pratikte karşına çıkacak durumlar şunlar. obj.method() biçiminde bir method çağrısında this, noktanın solundaki object'tir. Düz bir fn() çağrısında strict mode'da undefined, klasik script'te ise global object, yani window olur. call, apply ya da bind kullanırsan this'i sen belirlersin. Arrow function'ın ise kendi this'i yoktur, çevresindekini kullanır. new ile çağrının this'ini de class'lar yazısında göreceğiz.

Method Çağrısı: Noktanın Solundaki Object

Şimdi baştaki soruya dönelim. İlk satır "Hello, I'm Ada" yazdırır, çünkü user.greet() çağrısında noktanın solunda user var. İkinci satır ise konsolda "Hello, I'm undefined" yazdırır. greet() düz bir çağrı; noktanın solunda hiçbir şey yok. Konsol klasik script gibi davrandığı için this, window oldu ve window.firstName diye bir şey yok. Aynı kod bir module'de ya da strict mode'da çalışsaydı this undefined olur ve TypeError alırdın.

Fonksiyon aynı fonksiyon; değişen tek şey çağrılma biçimi. Bunu daha net görmek için method'u başka bir object'e ödünç ver:

const admin = { firstName: "Grace" };
admin.greet = user.greet;
console.log(admin.greet()); // "Hello, I'm Grace"

Method user'ın içinde yazıldı ama admin üzerinden çağrıldı, dolayısıyla this admin. Fonksiyon kime ait olduğunu hatırlamaz; her çağrıda noktanın soluna bakılır. İç içe object'lerde de en yakın nokta geçerlidir: shop.cart.total() çağrısında this, shop değil shop.cart'tır.

Method Kaybı Tuzağı

Baştaki örneği kimse bilerek yazmaz ama aynı şey çok daha sinsi bir biçimde her gün olur: bir method'u callback olarak başka bir fonksiyona verdiğinde. Bunu konsola yapıştırıp dene:

const timer = {
  seconds: 0,
  tick() {
    this.seconds++;
    console.log(this.seconds);
  }
};

setTimeout(timer.tick, 100); // NaN

setTimeout'a timer.tick yazdığında fonksiyonun kendisini veriyorsun, object'le bağlantısını değil. Yüz milisaniye sonra setTimeout onu düz bir fonksiyon olarak çağırır. Tarayıcı bu durumda this'i window yapar, window.seconds tanımsız olduğu için ++ işlemi NaN üretir. Method, sahibini yolda kaybetti.

İki yaygın çözüm var. Ya çağrıyı bir arrow function'a sararsın, böylece method yine noktanın solundaki object'le çağrılır; ya da bir sonraki bölümlerde göreceğimiz bind'ı kullanırsın:

setTimeout(() => timer.tick(), 100);   // 1
setTimeout(timer.tick.bind(timer), 200); // 2

Aynı sorun method'u map'e, addEventListener'a ya da bir promise zincirine verdiğinde de çıkar. Kural basit: method'u parantezsiz bir yere veriyorsan, this'i de yanında götürmediğini bil.

Arrow Function'ın this'i

Arrow function'lar ES2015 ile geldi ve this konusunda bambaşka davranır: kendi this'leri yoktur. İçlerinde this geçtiğinde, sıradan bir değişken gibi, yazıldıkları yerin çevresine bakarlar. Yani arrow function'da this, daha önce gördüğümüz lexical scope kurallarına uyar. Bu, bir method'un içinde callback yazarken tam istediğimiz şeydir:

const team = {
  title: "Frontend",
  members: ["Ada", "Alan"],
  list() {
    return this.members.map((m) => `${m} - ${this.title}`);
  },
  listBroken() {
    return this.members.map(function (m) {
      return `${m} - ${this.title}`;
    });
  }
};

console.log(team.list());       // ["Ada - Frontend", "Alan - Frontend"]
console.log(team.listBroken()); // ["Ada - undefined", "Alan - undefined"]

list'teki arrow function, this'i list'ten alır; list de team.list() olarak çağrıldığı için bu team'dir. listBroken'daki sıradan fonksiyonu ise map düz bir fonksiyon olarak çağırır ve this kaybolur. Eski kodlarda bu sorunu çözmek için yazılan const self = this; satırını görürsen, şimdi nedenini biliyorsun.

Madalyonun öbür yüzü: arrow function'ı object'in method'u olarak yazmak neredeyse her zaman hatadır.

const scoreBoard = {
  score: 10,
  show: () => this.score
};
console.log(scoreBoard.show()); // undefined

Buradaki this object'e değil, object'in yazıldığı çevreye, yani global scope'a ait. Kural kısaca şöyle: method'lar için kısa method yazımını, method'ların içindeki callback'ler için arrow function'ı kullan.

call ve apply: this'i Elle Vermek

Bazen bir fonksiyonu belirli bir this ile çağırmak istersin. Her fonksiyonun üzerinde bunun için iki method var. call ilk argümanı this olarak kullanır, geri kalanları fonksiyona tek tek iletir. apply aynı işi yapar ama argümanları bir array içinde alır:

function introduce(greeting, punctuation) {
  return `${greeting}, I'm ${this.firstName}${punctuation}`;
}

const ada = { firstName: "Ada" };
const alan = { firstName: "Alan" };

console.log(introduce.call(ada, "Hi", "!"));        // "Hi, I'm Ada!"
console.log(introduce.apply(alan, ["Hello", "."])); // "Hello, I'm Alan."

Akılda tutmanın kolay yolu: Apply, Array ile; Call, virgülle (Comma). Spread geldikten sonra apply'a eskisi kadar ihtiyaç kalmadı. introduce.call(alan, ...args) aynı işi görür; eskiden yazılan Math.max.apply(null, numbers) da bugün Math.max(...numbers) oldu.

bind: this'i Kalıcı Olarak Bağlamak

call ve apply fonksiyonu hemen çalıştırır. bind ise çalıştırmaz; this'i kalıcı olarak sabitlenmiş yeni bir fonksiyon döndürür. Sonra o fonksiyonu nereye verirsen ver, nasıl çağırırsan çağır, this değişmez:

const introduceAda = introduce.bind(ada);
console.log(introduceAda("Hey", "?"));           // "Hey, I'm Ada?"
console.log(introduceAda.call(alan, "Hi", "!")); // "Hi, I'm Ada!", hâlâ Ada

Method kaybı tuzağındaki timer.tick.bind(timer) çözümü de buydu. bind'ın ikinci bir marifeti daha var: this'ten sonra verdiğin argümanları da sabitler. Buna partial application (kısmi uygulama) denir. Bir fonksiyonun bazı argümanlarını önceden doldurup daha özel bir fonksiyon üretirsin:

function calcPrice(taxRate, price) {
  return price * (1 + taxRate);
}

const withVat = calcPrice.bind(null, 0.2);
console.log(withVat(100)); // 120
console.log(withVat(250)); // 300

const sayHiAsAlan = introduce.bind(alan, "Hi");
console.log(sayHiAsAlan("!!")); // "Hi, I'm Alan!!"

calcPrice this kullanmadığı için ilk argümana null verdik. Aynı işi (price) => calcPrice(0.2, price) gibi bir arrow function ile de yapabilirsin; ikisi de geçerli, ekip hangisini daha okunaklı buluyorsa o. Bir uyarı: arrow function'lar call, apply ve bind'a verilen this'i görmezden gelir; onların this'i değiştirilemez.

Event Handler'larda this

Daha önce gördüğümüz addEventListener, sıradan bir fonksiyonu listener olarak çağırırken this'i listener'ın bağlandığı element yapar. Arrow function'da ise yine çevredeki this geçerlidir. Bu küçük HTML dosyasını kaydedip tarayıcıda aç ve iki butona da tıkla:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>this and events</title>
</head>
<body>
  <button id="save">Save</button>
  <button id="cancel">Cancel</button>
  <script>
    const saveBtn = document.getElementById("save");
    saveBtn.addEventListener("click", function () {
      console.log(this === saveBtn); // true
      this.textContent = "Saved";
    });

    const orderForm = {
      label: "Order form",
      init() {
        document.getElementById("cancel").addEventListener("click", () => {
          console.log(`${this.label} cancelled`); // this burada orderForm
        });
      }
    };
    orderForm.init();
  </script>
</body>
</html>

İlk listener'da this butonun kendisi. İkincisinde arrow function this'i init'ten aldığı için orderForm'a ulaşabiliyoruz. Elementi this yerine event.currentTarget ile almak da mümkün; hem arrow function'da çalışır hem de okuyan kişiye ne kastettiğini daha açık söyler.

Sık Düşülen Tuzaklar

Konsolda çalışıp module'de bozulan kod. Konsol ve klasik script'ler varsayılan olarak strict mode'da değildir; module'ler ise her zaman strict'tir. Düz çağrıda this konsolda window, module'de undefined olur. Aynı kod bir yerde undefined yazdırırken diğerinde TypeError fırlatabilir.

Method'u doğrudan listener yapmak. button.addEventListener("click", cart.add) yazdığında add içindeki this artık cart değil, butondur. bind ile düzeltirken de dikkat: bind her çağrıldığında yeni bir fonksiyon üretir. removeEventListener("click", cart.add.bind(cart)) hiçbir şeyi kaldırmaz, çünkü eklediğin fonksiyonla aynı fonksiyon değildir. Bağlanmış fonksiyonu bir değişkende sakla ve hem eklerken hem kaldırırken onu kullan.

Method içindeki sıradan iç fonksiyon. Bir method'un içinde function () { ... } ile yazdığın yardımcı fonksiyon, dıştaki method'un this'ini miras almaz. Callback'ler için arrow function kullan.

this'i fonksiyonun kendisi sanmak. this, içinde bulunduğu fonksiyonu değil, çağrının bağlamını gösterir. Fonksiyonun kendisine ulaşmak istiyorsan ona adıyla seslen.

Küçük Bir Deneme

★☆☆ Çalıştırmadan önce tahmin et: const box = { size: 5, getSize() { return this.size; } }; const getSize = box.getSize; console.log(box.getSize(), getSize()); Konsolda çalıştırınca ne görürsün, bir module'de çalışsaydı ne olurdu?

★★☆ prefix property'si ve log(message) method'u olan bir logger object'i yaz. logger.log'u setTimeout'a önce olduğu gibi ver ve bozulduğunu gör. Sonra arrow function ve bind ile iki ayrı yoldan düzelt.

★★☆ applyDiscount(percent, price) fonksiyonunu yaz ve bind ile discount10 ve discount25 adında iki yeni fonksiyon üret. [100, 250, 80].map(discount10) sonucunu tahmin et ve dene. map callback'e index'i de verdiği hâlde burada neden parseInt'teki gibi bir sorun çıkmadığını açıkla.

★★★ Küçük bir HTML dosyasında count ve increment() içeren bir clicker object'i yaz. increment'i bir butonun click listener'ı yap, her tıklamada sayıyı butonun üzerinde göster ve beşinci tıklamadan sonra listener'ı removeEventListener ile kaldır.

Bu yazıda object'leri method taşıyan yapılar olarak kullandık. Ama bazen bir object'i sadece key-value deposu olarak kullanırız ve o zaman object'lerin sınırlarına çarparız: key'ler string'e dönüştürülür, kayıt sayısını öğrenmek ve üzerinde dolaşmak zahmetlidir. Bir sonraki yazıda bu iş için tasarlanmış iki koleksiyonu, Map ve Set'i, ES2025 ile gelen yeni Set method'larıyla birlikte göreceğiz.