Objelerin metodları ve "this" kelimesi.

Objeler genelde dünyada var olan şeyler gibidirler, kullanıcılar, emirler, vs.

let kullanici = {
  isim: "İhsan",
  yas: 30
};

Kullanıcıların işlem yapma yetenekleri vardır: alışveriş sepeti, giriş, çıkış vs.

Bu aksiyonlar Javascript’te özellikler için fonksiyon kullanarak çözülür.

Metod Örnekleri

Başlangıç olarak kullanici merhaba desin:

let kullanici = {
  isim: "İhsan",
  yas: 30
};

kullanici.selamVer = function() {
  alert("Merhaba");
};

kullanici.selamVer(); // Merhaba

Burada Fonksiyon ifadesi ile fonksiyon yaratıldı ve kullanici.selamVer özelliğine atandı.

Ardından bu metod çağırıldı ve kullanıcı selam verdi.

Bir objenin özelliği olan fonksiyona metod denir.

Öyleyse kullanici objesinin selamVer metodu bulunmaktadır.

Tabii ki metodları önceden tanımlanmış fonksiyonlarla da oluşturabilirsiniz. Örneğin:

let kullanici = {
  // ...
};

// önce tanımla
function selamVer() {
  alert("Merhaba!");
};

// Sonra bunu metod olarak objeye ekle
kullanici.selamVer = selamVer;

kullanici.selamVer(); // Merhaba!
Nesne Tabanlı Programlama

Varlıkların obje olarak tanımlandığı dillere obje tabanlı diller, kısaca: “OOP”(Objet-Oriented Programming)

OOP kendi başına kitaplar dolusu anlatılacak bir konudur. Nasıl doğru varlıklar seçilmeli? Bu varlıklar arasında nasıl bir iletişim olmalı? Mimarisi nasıl olmalı, gibi konuların her birisi ile ilgili ayrı ayrı kitaplar bulunmaktadır. Örneğin “Design Patterns: Elements of Reusable Object-Oriented Software” by E.Gamma, R.Helm, R.Johnson, J.Vissides or “Object-Oriented Analysis and Design with Applications” by G.Booch, vs. Bu kitapta "object-oriented-programming" adresindeki makale bulunamadı sadece başlangıç seviyesinde anlatılacaktır.

Metod Kısayolu

Metodları yaratmak için daha kolay bir kullanım mevcuttur:

// aşağıdaki objeler aynı işleri yapar.

let kullanici = {
  selamVer: function() {
    alert("Merhaba");
  }
};

// kısa yolu daha güzel görünüyor değil mi?
let kullanici = {
  selamVer() { // "selamVer: function()" ile aynı
    alert("Merhaba");
  }
};

Yukarıda da gösterildiği gibi "function" pas geçilerek sadece selamVer() yazılabilir.

Doğrusunu söylemek gerekirse iki fonksiyonda birbiri ile aynı. Altta yatan farklılık ise kalıtım ile alakalı ki bu da şimdilik bir sorun teşkil etmiyor. Çoğu durumda kısa yazım tercih edilmektedir.

Metodlarda this kullanımı

Obje metodlarının objelerde bulunan diğer bilgilere ulaşması çok büyük bir gerekliliktir. Örneğin kullanici.selamVer() kullanici ismine ihtiyaç duyar.

Objeye ulaşabilmek içim metod this kelimesine ihtiyaç duyar.

“noktadan önce” yazılan this o objeye referans verir. Örneğin:

let kullanici = {
  isim: "İhsan",
  yas: 30,

  selamVer() {
    alert(this.isim);
  }

};

kullanici.selamVer(); // İhsan

Yukarıda kullanici.selamVer() fonksiyonu çalıştırılırken this kullanici olacaktır.

Teknik olarak this olmadan da objenin özelliklerine erişmek mümkündür. Bu dıştaki değişkene referans vererek yapılabilir:

let kullanici = {
  isim: "İhsan",
  yas: 30,

  selamVer() {
    alert(kullanici.isim); // "this" yerine "kullanici" kullanılmıştır.
  }

};

… Fakat böyle bir koda güvenilez. Diyelim ki kullanici objesini kopyaladınız ve yonetici = kullanici yaptınız. Sonra kullanici objesinin üzerine yazdınız bu durumda yanlış objeye erişmiş olacaksınız. Bir örnekle açıklamak gerekirse:

let kullanici = {
  isim: "İhsan",
  yas: 30,

  selamVer() {
    alert( kullanici.isim ); // hataya neden olur
  }

};


let yonetici = kullanici;
kullanici = null;

yonetici.selamVer(); // `selamVer()` içerisinde `kullanici` kullanıldığından dolayı hata verecektir.

Eğer kullanici.isim yerine this.isim yazmış olsaydınız kod çalışacaktı.

“this” bağımsız bir şekilde kullanılabilir.

Diğer dillerden farklı olarak “this” kelimesi yer gözetmeksizin kullanılabilir. Her fonksiyonun içinde kullanılabilir.

Aşağıdaki kodda bir yazım hatası yoktur:

function selamVer() {
  alert( this.isim );
}

this'in değeri çalışma anında değerlendirilir. Herşey olabilir.

Örneğin this farklı objelerden çağırıldıklarında değerler alabilirler:

let kullanici = { isim: "İhsan" };
let yonetici = { isim: "Macide" };

function selamVer() {
  alert( this.isim );
}

// aynı fonksiyonu iki farklı objeye atandı.
kullanici.f = selamVer;
yonetici.f = selamVer;

// iki fonksiyon da farklı `this` e sahip.
// "noktadan" önceki "this" objeye referans verir.
kullanici.f(); // İhsan  (this == kullanici)
yonetici.f(); // Yonetici  (this == yonetici)

yonetici['f'](); // Köşeli parantez veya noktalı yazım farketmez, her ikisi de çalışır.

Aslında fonksiyonu obje olmadan da çağırmak mümkündür.

function selamVer() {
  alert(this);
}

selamVer(); // tanımsız

Sıkı modda this undefined döndürür. Eğer this.isim yazılırsa hata verir.

Normal modda ise ( use strict unutulursa) this değeri global obje olur. Tarayıcı için bu windowdur. Bu konuya daha sonra değinilecektir.

Obje olmadan this çağırmak normal değildir, bir programlama hatasıdır. Eğer fonksiyon this içeriyorsa, o objenin dahilinde çağırılabileceği anlamı çıkar.

```smart header="Sınırsız `this` kullanmanın yan etkileri"

Diğer programlama dillerinden geliyorsanız, "bağımlı `this`" kullanımına alışmış olmalısınız. Metod içerisinde kullanılan `this`  her zaman o objeye referans olur.

JavaScript'te `this` bağımsızdır. Değeri çalışma anında belirlenir, hangi metodda yazıldığı önemli değildir, önemli olan "noktadan önceki" objedir.

Çalışma anında `this` kullanılabilmesinin artıları ve eksileri vardır. Bir taraftan fonksiyonlar diğer objelerde de kullanılabilirken, diğer yönden bu kadar esneklik hatalara neden olabilmektedir.

Burada amacımız programlama dilininin dizaynının kötü veya iyiliği değildir. Amaç nasıl çalıştığını anlayıp, nerelerde yarar nerelerde zarar getirileceğini bilmektir.

```

## Dahili Özellikler: Referans Tipleri

```warn header="Derinlemesine dil özellikleri"
Bu bölüm daha derinlemesine konuları içermektedir.

Daha hızlı ilerlemek istiyorsanız bu bölümü geçebilir veya daha sonraya erteleyebilirsiniz.
```
Girift metod çağrıları `this` kelimesini kaybedebilir, örneğin:

```js run
let kullanici = {
  isim: "İhsan",
  selamVer() { alert(this.isim); },
  yolcuEt() { alert("Güle Güle"); }
};

kullanici.selamVer(); // Basit metod beklendiği gibi çalışır

// Şimdi isme göre selamVersin veya yolcuEt'sin.
(kullanici.isim == "İhsan" ? kullanici.selamVer : kullanici.yolcuEt)(); // Hata!
```

Son satırda kullanıcı ismine göre `kullanici.selamVer` veya `kullanici.yolcuEt` cagrilir. `kullanici.selamVer` `()` ile çağrıldığında çalışmaz.

Bunun nedeni çağrı içerisinde `this`'in `undefined` olmasıdır.

Aşağıdaki çalışır:
```js
kullanici.selamVer();
```
Aşağıdaki kod metodu çalıştırmaz:
```js
(kullanici.isim == "İhsan" ? kullanici.selamVer : kullanici.yolcuEt)(); // Hata!
```

Neden? Eğer bunun derinliklerine inmek isterseniz öncelikle bakmanız gereken yer `obj.method()` çağrısı olmalıdır.

Peki bu `obj.method()` cümlesi ne yapar:

1. `'.'` özelliği alır
2. `()` bu özelliği çalıştırır.

Peki `this` ilk bölümden ikincisine nasıl geçer?

Eğer bu olayı iki farklı satırda gösterecek olursak, `this` bu durumda kaybolacaktır:

```js run
let kullanici = {
  isim: "İhsan",
  selamVer() { alert(this.isim); }
}

// metodu alma ve çağırma iki satırda gösterilecek olursa
let selamVer = kullanici.selamVer;
selamVer(); // hata!, tanımsız
```
Burada `selamVer = kullanici.selamVer` fonksiyonu değişkene atar, sonra son satırdaki yapılan ise tamamen ilkinden farklıdı. Bundan dolayı `this` bulunmamaktadır.

**`kullanici.selamVer()` çağrısının çalışabilmesi için bir çözüm bulunmaktadır. `'.'` fonksiyon değil [Referans Tipi] döndürmektedir.(https://tc39.github.io/ecma262/#sec-reference-specification-type)**

Referans Tipi "şartname tipidi". Doğrudan kullanılamaz, dil kendince kullanabilir bu tipleri.

Referans Tipi üç değerin birleşmesi ile oluşur `(base, name, strict)`:

- `base` objedir.
- `name` özelliktir.
- `strict` eğer `use strict` kullanılmışsa `true` olur.

`kullanici.selamVer` erişimi fonksiyon değil Referans Tipi döndürür. Sıkı mod kullanıldığında `kullanici.selamVer` aşağıdaki gibi döner:

```js
// Referans Tipi değeri
(kullanici, "selamVer", true)
```

Referans Tipinde `()` çağrıldığında obje ve onun metodu hakkında tüm bilgileri alınır ve `this` (bizim durumumuzda `kullanici` ) belirlenir.

Atama gibi işlemler `selamVer = kullanici.selamVer` referans tipini tamamen iptal eder, `kullanici.selamVer`(fonksiyon) değerini alır ve bunu paslar. Bu şekilde de `this` tanımsız kalır.

Bundan dolayı `this` in çalışabilmesi için metodun doğrudan `obj.metod()` şeklinde veya `obj[metod]()` şeklinde çalıştırılması gerekmektedir.

## Ok fonksiyonlarında "this" bulunmamaktadır.

Ok fonksiyonları özeldir: Kendilerinin `this`'i bulunmaz. Eğer yine de `this` kullanırsanız ok fonksiyonu dışındaki bölümü `this` olarak alır.

Örneğin aşağıdaki `ok()` dışarıda bulunan `kullanici.selamVer()` metodunu kullanmaktadır:


```js run
let kullanici = {
  isim: "İhsan",
  selamVer() {
    let ok = () => alert(this.isim);
    ok();
  }
};

kullanici.selamVer(); // İhsan
```
Bu ok fonksiyonlarının bir özelliğidir. Ayrı bir `this` kullanmak yerine her zaman bir üstteki bölümden `this` i alması baya kullanışlıdır. <info:arrow-functions> bölümü içerisinde bu konu derinlemesine incelenecektir.

## Özet

- Objeler içerisinde saklanan fonksiyonlara "metod" denir.
- Metodlar objelerin `obje.biseylerYap()` seklinde çalışabilmesini sağlar.
- Metodlar objelere `this` şekline referans verebilir.

`this`'in değeri çalışma zamanında tanımlanır.
- Fonksiyon tanımlanırken `this` kullanabilir, fakat `this` bu metod çalışmadığı müddetçe bir anlam ifade etmez.
- O fonksiyon objeler arasında kopyalanabilir.
- Fonksiyon metod yazım şekliyle çağırıldığında `obje.metod()`, `this`'in değeri bu çağrı boyunca `obje`'dir.

Ok fonksiyonlarında `this` bulunmamaktadır. Eğer bu fonksiyonlar içerisinde `this` çağırılırsa bunun değeri dışarıdan alınır.

Görevler

önem: 2

Aşağıdaki kodun çıktısı nedir?

let kullanici = {
  isim: "İhsan",
  selamVer: function() { alert(this.isim) }
}

(kullanici.selamVer)()

Not: Dikkatli düşün :)

Hata!

Deneyebilirsiniz:

let kullanici = {
  isim: "John",
  selamVer: function() { alert(this.isim) }
}

(kullanici.selamVer)() // error!

Tarayıcıların çoğundaki hata mesajının anlaşılmaz.

The error appears because a semicolon is missing after kullanici = {...}.

JavaScript parantezden önce noktalı virgül koymaz. Bu durumda kod aşağıdaki gibi çalışacaktır:

let kullanici = { selamVer:... }(kullanici.selamVer)()

Bu şekilde çağırıldığında, let kullanici ile çağrı aynı satırda yapılmış olur. Ayrıca bu tanımlanan fonksiyon (kullanici.selamVer) şeklinde aynı satırda argüman olarak kullanılmıştır. Bundan dolayı da hata oluşmaktadır.

Eğer noktalı virgül koyarsanız herşey beklediğiniz gibi çalışır:

let kullanici = {
  isim: "İhsan",
  selamVer: function() { alert(this.isim) }
};

(kullanici.selamVer)() // John

(kullanici.selamVer) etrafındaki parantez bir işe yaramaz. Sadece sıralamayı belirtir. Bu soruda ; önemliydi.

önem: 3

Aşağıdaki amaç obj.selamVer() in 4 defa çağırılmasıdır.

Fakat (1) ve (2) , (3) ve (4) ten farklı çalışmaktadır. Neden?

let obj, metod;

obj = {
  selamVer: function() { alert(this); }
};

obj.selamVer();               // (1) [object Object]

(obj.selamVer)();             // (2) [object Object]

(metod = obj.selamVer)();    // (3) undefined

(obj.selamVer || obj.yolcuEt)(); // (4) undefined

Açıklama:

  1. Normal metod çağrısı yapılmaktadır.

  2. Aynı şekilde çağrı yapılmaktadır. Tek fark parantez ve bu parantez sadece sıralama için kullanılmaktadır. Bir anlam ifade etmez.

  3. Daha karmaşık bir çağrı, (ifade).metod(). Eğer bu metod iki satıra ayrılırsa çalışır:

    f = obj.selamVer; // ifadeyi hesapla
    f();        // çağır

    Burada f() fonksiyon olarak this ifadesi olmadan çalıştırılmıştır.

  4. Aynı şekilde . nın sol tarafında bir ifade bulunmaktadır.

(3) ve (4) ün davranışını açıklamak için dönen referans tipini tekrar çalıştırmak gereklidir.

Metod çağrısı haricinde her işlem( atama = veya ||) bu fonksiyonu normal değere döndürür. Bundan dolayı da this'in tanımsız kalmasına yol açar.

önem: 5

Aşağıda kullaniciOlustur obje döndermektedir.

ref'e ulaşıldığında ne döner? Neden?

function kullaniciOlustur() {
  return {
    isim: "İhsan",
    ref: this
  };
};

let kullanici = kullaniciOlustur();

alert( kullanici.ref.isim ); // Sonuç nedir?

Cevap: Hata.

Deneyin:

function kullaniciOlustur() {
  return {
    isim: "İhsan",
    ref: this
  };
};

let kullanici = kullaniciOlustur();

alert( kullanici.ref.isim ); // Error: Cannot read property 'name' of undefined

Çünkü this'i tanımlayan kurallar obje tanımına bakmaz.

Kodda kullaniciOlustur() içindeki this undefined'dır. Çünkü bu fonksiyon olarak çağırıldı metod olarak değil!.

Ve objet tanımının this'e doğrudan bir etkisi yoktur. this tüm fonksiyonu kapsar, kod bloğu veya obje tanımı bunu etkilemez.

Öyleyse, ref: this aslında fonksiyonun thsi değerini alır.

Şimdi tersi bir duruma bakalım:

function kullaniciOlustur() {
  return {
    isim: "İhsan",
    ref() {
      return this;
    }
  };
};

let kullanici = kullaniciOlustur();

alert( kullanici.ref().isim ); // İhsan

Şimdi çalışıyor çünkü kullanici.ref() metod oldu. this'in değeri de . dan öncesi olarak tanımlandı.

önem: 5

hesapMakinesi objesini aşağıdaki üç metod ile oluşturunuz:

  • oku() veri giriş ekranı gösterir ve iki değeri objenin özelliklerine kaydeder.
  • topla() kaydedilen değerlerin toplamını döner.
  • carp() kaydedilen değerlerin çarpımını döner.
let hesapMakinesi = {
  // ... sizin kodunuz ...
};

hesapMakinesi.oku();
alert( hesapMakinesi.topla() );
alert( hesapMakinesi.carp() );

Demoyu çalıştır

Testler ile korunaklı olan aç.

let hesapMakines = {
  sum() {
    return this.a + this.b;
  },

  mul() {
    return this.a * this.b;
  },

  read() {
    this.a = +prompt('a?', 0);
    this.b = +prompt('b?', 0);
  }
};

calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );

Çözümü testler korunaklı alanda olacak şekilde aç.

önem: 2

merdiven objesi yukarı aşağı harekete izin vermektedir:

let merdiven = {
  adim: 0,
  yukari() {
    this.adim++;
  },
  asagi() {
    this.adim--;
  },
  adimiGoster: function() { // o anki adımı gösterir
    alert( this.adim );
  }
};

Eğer aşağıdaki gibi ard arda çağrı yapılırsa:

merdiven.yukari();
merdiven.yukari();
merdiven.asagi();
merdiven.adimiGoster(); // 1

yukari ve asagi metodlarını aşağıdaki gibi zincirleme yapılabilir hale getiriniz:

merdiven.yukari().yukari().asagi().adimiGoster(); // 1

Bu yaklaşım çoğu JavaScript kütüphanesinde yaygın olarak kullanılmaktadır.

Testler ile korunaklı olan aç.

Çözüm her metod çağrısı sonrası kendisini döndermektir.

let merdiven = {
  adim: 0,
  yukari() {
    this.adim++;
    return this;
  },
  asagi() {
    this.adim--;
    return this;
  },
  adimiGoster() {
    alert( this.adim );
    return this;
  }
}

merdiven.yukari().yukari().asagi().yukari().asagi().adimiGoster();//1

Ayrıca her satır için tek çağrı da yazılabilir. Uzun zincirleme fonksiyonlar için bu daha okunabilirdir.

merdiven
  .yukari()
  .yukari()
  .asagi()
  .up()
  .asagi()
  .adimiGoster(); // 1

Çözümü testler korunaklı alanda olacak şekilde aç.

Eğitim haritası

Yorumlar

yorum yapmadan önce lütfen okuyun...
  • Eğer geliştirme ile alakalı bir öneriniz var ise yorum yerine github konusu gönderiniz.
  • Eğer makalede bir yeri anlamadıysanız lütfen belirtiniz.
  • Koda birkaç satır eklemek için <code> kullanınız, birkaç satır eklemek için ise <pre> kullanın. Eğer 10 satırdan fazla kod ekleyecekseniz plnkr kullanabilirsiniz)