Javascript’in This Keyword’ünü Anlamak

Ayse Basar
7 min readMay 15, 2023

--

Javascript’in This Keyword’ü sanırım anlaşılması en zor konularından biri. Benim de uzun bir zaman boyunca tam olarak ne olduğunu bilmediğim ve anlayamadığım bir konu oldu. O yüzden bu konuyu seçtim ve sadece ben değil anlamakta zorluk çeken herkesin de anlayabilmesi için öğrendiklerimi paylaşmak istedim.

Öncelikle this’in değeri ne, neye göre belirleniyor, neyi ifade ediyor bence anlaması en zor kısmı bu. O yüzden ilk olarak belirtmek isterim ki this’in değerini belirleyen 4 kural var. Bunlar :

  1. Default Binding
  2. Implicit Binding
  3. Explicit Binding
  4. New Binding

olarak adlandırılıyor.

Şimdi bunları tek tek örneklerle açıklayalım

Default Binding

Bir örnekle bunu açıklamaya başlayalım :

function sayHi() {
console.log(this);
}
sayHi();

Yukarıdaki fonksiyon Javascript’te normal, regular bir fonksiyon olarak adlandırılmaktadır. Function keyword kullanılarak oluşturulmuştur ve çağrılma şekli sayHi() olarak kullanılmıştır. Herhangi bir objenin parçası değildir. Bu tür fonksiyonlar Javascript’in global scope’unda yer alır. Çünkü global scope’tan çağrılırlar. Fonksiyonun kapsayıcısı (parent’i) Javascript’in global window objesidir.

Dipnot: — Regular Fonksiyonlar ismi olan, function keyword ile oluşturulan ve bir objeye bağlı olarak çağrılmayan fonksiyonlardır —

Console çıktısına bakacak olursak bunu görebileceğiz.

Sonuç: Regular Fonksiyonlar global objeyi değer olarak döndürür. This değeri global window objesidir — İşte bu default binding’dir.

Implicit Binding

Yine bir örnek üzerinden açıklamaya başlayalım :

const greet = {
id: 1,
sayHi: sayHi,
}

greet.sayHi()

Bu sefer bir obje oluşturduk ve yukarıdaki sayHi fonksiyonumuzu objemizin içine key-value olarak yerleştirdik. Bu fonksiyonu greet.SayHi() olarak çağırabileceğiz. Çünkü artık bir objenin parçası haline geldi. Eğer bir fonksiyon bir objenin parçası ise biz bu fonksiyonları metod olarak adlandırırız. Eğer bir fonksiyon bir objede metod ise o zaman burada this, objenin kendisini yani daha açık bir şekilde noktanın solundaki objeyi ifade eder. Yani greet objesini. Artık fonksiyonun kapsayıcısı (parent’i) greet objesidir.

Hemen console çıktısına bakacak olursak bunu görebileceğiz.

Sonuç: Implicit Binding kuralına göre bir fonksiyon nokta ile çağrıldığında this, noktanın solundaki nesneyi değer olarak alır.

Explicit Binding

Bazı durumlarda fonksiyonlarımızı bir objeye metod olarak ekleyemeyebiliriz. Belki obje bize ait değildir, belki bir kütüphaneden çektiğimiz bir objedir ya da immutable bir objedir ve değiştiremeyiz vs. Bu tür durumlarda Javascript’in bind/call/apply metodlarını kullanarak sorunu çözebiliriz. Buna da explicit binding adı verilir. Örnek verecek olursak

const person = {
name: 'Ayse',
};

function myName() {
console.log('My name is', this.name);
}

myName()

Yukarıda gördüğümüz üzere obje ile fonksiyonumuzun bir bağlantısı yok. Fonksiyonumuz name değerine ulaşamaz. Kendisi bir regular fonksiyon. Bu nedenle global objeye işaret edecek ve isim değerini döndürmeyecek. Çünkü global objede name adında bir değişken yok. Peki bu değeri almasını nasıl sağlayabiliriz ? Bahsettiğim metodlarla :

const person = {
name: 'Ayse',
};

function myName() {
console.log('My name is', this.name);
}

const sayMyName = myName.bind(person);
sayMyName();

Console çıktısına da bakalım:

New Binding

New Binding kısmında ise başka bir fonksiyon türüne bakmamız gerek: Constructor Fonksiyonlar. Hemen gözümüz korkmasın :) Bir örnek üzerinden gidelim

function Book(title) {
this.title = title;
console.log(this);
}

new Book('Harry Potter and The Goblet of Fire') // {}

Constructor Fonksiyon nedir burada açıklamayacağım ama kısaca yukarıdaki fonksiyonumuz bir Constructor Fonksiyondur. New operatörü kullanılarak oluşturulmuştur ve fonksiyon isminin ilk harfi büyüktür.

New Operatörü yeni bir boş nesne oluşturur ve bu objeyi döndürür. Constructor fonksiyondaki “this”i de bu boş nesneye işaret edecek şekilde ayarlar. Buna new binding denir. Yine console çıktımıza bakalım:

Sonuç: Constructor fonksiyonlar yeni bir boş obje oluştur ve this değeri olarak bu objeyi döndürür.

Şimdi bahsetmek istediğim birkaç önemli nokta daha var

1.Arrow fonksiyonlar

Evet arrow fonksiyonlar this konusunda “function” ile tanımlanan fonksiyonlar gibi davranmıyorlar.

Arrow fonksiyonlar kendilerini içine alan en yakın üst (parent) fonksiyonun kapsamını (scope) miras alırlar. ( Lexical Scoping )

Hemen bir örnekle bakalım :

const user = {
firstName: 'Ayşe',
lastName: 'Başar',
fullName: () => {
console.log(this.firstName, this.lastName);
},
};
user.fullName();

Şimdi burada fullName fonksiyonu objemizin bir metodu. Implicit binding kuralına göre noktanın solundaki nesneyi göstermesi gerekiyor aslında bizlere. Bu “function” keyword kullanılarak yazılsaydı evet ( function fullName() {} ). Ama arrow fonksiyonlarda bu durum geçerli değil. Kendilerini kapsayan bir fonksiyon olması lazım. Burada arrow fonksiyonumuzu kapsayan herhangi bir regular fonksiyon bulunmamakta. Bu nedenle kendisi değer olarak window objesini alacak ve fullName değerine erişemeyecek. Çünkü global objede firstName ve lastName diye bir değişken yok. Bunlar user objesi içinde. Console çıktısına da bakacak olursak

Fonksiyonumuzun user objesini işaret edebilmesi için onu bir regular fonksiyon içine almamız gerekmektedir.

const user = {
firstName: 'Ayşe',
lastName: 'Başar',
fullName: function () {
// şimdi arrow fonksiyonumuzun bir kapsayıcısı var.
const arrowFunc = () => {
console.log(this.firstName, this.lastName);
};
arrowFunc();
},
};
user.fullName();

2. Callback Fonksiyonlar

Bir örnekle başlayalım :

const movie = {
title: 'Harry Potter and The Goblet of Fire',
genres: ['Fantastic', 'Thriller', 'Mistery'],
showGenres() {
this.genres.forEach(function (genre) {
console.log(genre, this.title);
});
},
};
movie.showGenres()

Şimdi, showGenres movie isimli objemizin parçası. Yani bir metod. Ve içinde genreleri döndüren bir forEeach fonksiyonu var. Bir de title değerine erişmek istiyoruz bu fonksiyonun içinde. Şimdi yine diyeceksiniz ki showGenres bir metod olduğuna göre movie objesine erişecek ve title değerini gösterecek. Console çıktısına bakalım

Title değerini döndürmedi. Acaba neden ?

Çünkü showGenres bir metod olmasına karşın içinde bir callback fonksiyon var: forEach. Bu callback fonksiyon da function keyword kullanılarak oluşturulmuş. Bu durumda ne demiştik ? Regular fonksiyonlar global window objesini işaret eder. Burada this değeri movie objesi değil global window objesi olacak ve bu nedenden dolayı undefined dönecek. Peki bunu nasıl çözebiliriz ?

  • Callback fonksiyona ikinci bir argüman olarak this vererek ya da
  • Callback fonksiyonu arrow fonksiyona çevirerek. Çünkü o zaman aşağıda göreceğiniz gibi etrafında kapsayıcı bir fonksiyon olacak
const movie = {
title: 'Harry Potter and The Goblet of Fire',
genres: ['Fantastic', 'Thriller', 'Mistery'],
showGenres() {
this.genres.forEach(function (genre) {
console.log(genre, this.title);
}, this); // ikinci argüman olarak this
},
};

// ya da

const movie = {
title: 'Harry Potter and The Goblet of Fire',
genres: ['Fantastic', 'Thriller', 'Mistery'],
showGenres() {
// arrow function
this.genres.forEach((genre) => {
console.log(genre, this.title);
});
},
};
movie.showGenres()

3. EventHandlers

This, Dom içerisinde bir eventHandler durumunda kullanıldığında hedeflenen elementin kendisini işaret eder. Bunu bir örnek üzerinden daha iyi anlayabileceğiz.

const button = document.querySelector("button")
button.textContent = "Click Me"

button.addEventListener("click", function() {
console.log(this)
})

Console çıktısına bakalım ve button elementinin direkt olarak kendisinin geldiğine dikkat edelim. Şimdi anlaşılır olmuş olmalı.

Bahsedeceklerim bu kadardı. Fakat daha iyi pekişmesi için biraz daha örneklerle alıştırma yapalım derim. Cevapları aşağıda vereceğim. Bakmadan tahmin etmeye çalışın :)

// Örnek #1
// Sizce fonksiyon çıktısı ne olur ?

const obj = {
id: 1,
outer: function() {
function inner(){
console.log(this);
}
inner();
},
}

obj.outer();
// Örnek #2
// Sizce fonksiyon çıktısı ne olur ?

function someFn() {
console.log(this);
}

const obj = {
id: 1,
someFn: someFn,
}

const newFn = obj.someFn;
newFn();
// Örnek #3
// Sizce fonksiyon çıktısı ne olur ?

const user = {
name: 'Ayşe',
greet() {
console.log("Hello", this.name);
},
fareWell: () => {
console.log("Goodbye", this.name);
},
};

user.greet()
user.fareWell()
// Örnek #4
// Sizce fonksiyon çıktısı ne olur ?

const user = {
name: 'Ayşe',
newUser: {
name: "Test",
sayMyName: function() {
console.log("Hello", this.name);
}
},
};

user.newUser.sayMyName()

Cevaplar

Örnek #1

Çıktı: window objesi

Neden ? : outer objemizin bir metodu. Ama dikkat edersek içinde başka bir fonksiyon var — function inner — Bu regular bir fonksiyon ve bir metod değil. Bu nedenden dolayı global objeye işaret eder.

Örnek #2

Çıktı: window objesi

Neden ? : Fonksiyonun çağrılma şekline dikkat edelim. Evet nesne.metod şeklinde kullanılmış. Bu implicit binding kuralına göre işlemeli çünkü objenin bir parçası diye düşünebilirsiniz. Ancak dikkat edersek bu kısım bir değişkene atanmış. Sonra da normal bir fonksiyon gibi çağrılmış — Regular fonksiyon çağrımı — Yani objenin parçası olarak çağrım yapılmamış. Bu nedenden dolayı global objeye işaret eder. Eğer değişkene atamasaydık o zaman objenin kendisine işaret edecekti. *** Herhangi bir obje metodunu bir değişkene atarsak fonksiyona bir referans yaratmış oluruz.

Örnek #3

Çıktı: Hello, Ayşe / Goodbye,

Neden ? : İlk fonksiyonumuz normal bir fonksiyon. İkinci fonksiyonumuz ise bir arrow fonksiyon ve kapsayıcısı yok.

Örnek #4

Çıktı: Hello Test

Neden ? : Çünkü fonksiyonumuz user objesi içinde newUser objesini kapsayıcı olarak alır.

Okuduğunuz için teşekkür eder keyifli okumalar dilerim 👩‍💻

--

--