31 Ocak 2022

Fonksiyon Objeleri, NFE

Bildiğiniz gibi JavaScript’te fonksiyonlar değerdir.

Her değerin bir tipi olduğuna göre fonksiyonun tipi nedir?

JavaScript’te fonksiyon bir objedir.

Daha iyi görselleyebilmek adına fonksiyonlara “aksiyon objeleri” denebilir. Sadece çağırarak değil, obje olarak da davranabilirsiniz: özellik ekleme çıkarma, referans paslama vs.

“name” özelliği

Fonksiyon objelerinin kullanışlı özellikleri bulunmaktadır.

Örneğin, fonksiyonun ismi “name” özelliği ile alınabilir.

function selamVer() {
  alert("Selam");
}

alert(selamVer.name); // selamVer

“name” özelliği atama o kadar akıllıdır ki, fonksiyon tanımlama ifadelerindeki ismi bile doğru alır.

let selamVer = function() {
  alert("Selam");
}

alert(selamVer.name); // selamVer

Hatta atama varsayılan değer ile yapıldığında bile çalışır:

function f(selamVer = function() {}) {
  alert(selamVer.name); // selamVer (çalıştı!)
}

f();

Tanımda bu özelliğe “bağlamsal isim” denir. Eğer fonksiyonda böyle bir şey yoksa, tanımlama bunu içerikten alır.

Nesne metodlarının da isimleri vardır:

let kullanici = {

  selamVer() {
    // ...
  },

  yolcuEt: function() {
    // ...
  }

}

alert(kullanici.selamVer.name); // selamVer
alert(kullanici.yolcuEt.name); // yolcuEt

Burada bir sihir yoktur. İsmin çıkarılamadığı birçok durum meydana gelebilir.

Böyle durumlarda aşağıdaki gibi boş dönerler:

// Dizinin içerisinde fonksiyon yaratılması
let arr = [function() {}];

alert( arr[0].name ); // <boş>
// motorun doğru ismi bulmasına imkan yok bundan dolayı boş dönüyor.

Pratikte çoğu fonksiyonun ismi bulunmaktadır.

“length” özelliği

“length” adında ayrı bir özellik daha bulunmaktadır. Bu özellik fonksiyon parametrelerinin sayısını döndürür:

function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}

alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2

Gördüğünüz gibi geriye kalan parametresi ... sayılmamaktadır.

length özelliği bazen diğer fonksiyonların üzerinde çalışan fonksiyonlara bir iç bakış oluşturur.

Mesela, aşağıdaki kodda sorfonksiyonu soru parametresi alır ve belirli olmayan sayıda isleyici fonksiyonunu çağırır.

Kullanıcı cevap verdiğinde isleyici(handler) çağırılır. İki türlü işleyici gönderilebilir:

  • Argümansız fonksiyon, sadece pozitif cevaplarda çağırılır.
  • Argümanlı fonksiyonlar, cevap alınan her durumda çağırılır.

Mantık şu şekildedir; cevap pozitif olduğunda argüman almayan isleyici calisir, fakat evrensel isleyiciye de izin verir.

isleyici’lerin doğru çalışması için, length özelliğinden faydalanılabilir.

function sor(soru, ...isleyiciler) {
  let dogruMu = confirm(soru);

  for(let isleyici of isleyiciler) {
    if (isleyici.length == 0) {
      if (dogruMu) isleyici();
    } else {
      isleyici(dogruMu);
    }
  }

}

// Olumlu cevap için, her ikisi çalışırken
// Olumsuz cevap için sadece ikincisi çalışmaktadır.
sor("Soru?", () => alert('Evet dedin'), sonuc => alert(sonuc));

Bu duruma polymorphism denilmektedir. Bu argümanlara tiplerine göre farklı davranma olayıdır. Bu bizim durumumuzda length’e bağlıdır. Bu fikir JavaScript kütüphanelerinde de kullanılmaktadır.

Özelleştirilmiş özellikler

Kendi özelliğinizi eklemek de mümkündür. Örneğin aşağıda counter özelliği ile toplan çağrı sayısı tutulmaktadır:

function selamVer() {
  alert("Selam");

  // Kaç defa çağırıldığını tutar.
  selamVer.counter++;
}
selamVer.counter = 0; // ilk değer

selamVer(); // Selam
selamVer(); // Selam

alert( `selamVer ${selamVer.counter} defa çağırılmıştır` ); // selamVer 2 defa çağırılmıştır.
Özellik değişken değildir

Fonksiyona atanan selamVer.counter = 0 selamVer fonksiyonunun içerisinde counter değişkenini tanımlamaz. Diğer bir deyişle counter özelliği ile let counter birbirinden tamamen farklı şeylerdir.

Fonksiyona obje gibi davranıp özellik eklenebilir. Bu çalışmasında bir etki yaratmaz. Değişkenler fonksiyon özelliklerini kullanmaz, keza fonksiyon özellikleri de değişkenleri kullanmaz.

Fonksiyon özellikleri closure kullanılarak tekrardan yazılabilir. Örneğin yukarıdaki sayaç örneğini Closure kullanarak tekrardan yazacak olursak:

function makeCounter() {
    // let count = 0 yazmak yerine

  function counter() {
    return counter.count++;
  };

  counter.count = 0;

  return counter;
}

let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1

count artık fonksiyonun içerisinde bulunur, dış ortamda değil.

Closure kullanmak iyi mi kötü mü?

Eğer sayac’ın değeri dışarıdaki değişkende bulunuyorsa, dışta bulunan kod buna erişemez. Sadece içteki fonksiyon bunu modifiye edebilir. Bu da anca fonksiyona bağlıysa gerçekleşebilir:

function makeCounter() {

  function counter() {
    return counter.count++;
  };

  counter.count = 0;

  return counter;
}

let counter = makeCounter();

counter.count = 10;
alert( counter() ); // 10

Bundan dolayı asıl önemli olan sizin hangi şekilde kullanmak istediğiniz.

İsimlendirilmiş Fonksiyon İfadeleri ( Named Function Expression – NFE )

İsimlendirilmiş fonksiyon ifadeleri , NFE, daha önce kullandığımız Fonksiyon İfadelerinin isimlendirilmiş halidir.

Örneğin, sıradan bir Fonksiyon İfadesi incelenecek olursa:

let selamVer = function(kim) {
  alert(`Selam, ${kim}`);
};

… isimlendirilirse:

let selamVer = function func(kim) {
  alert(`Merhaba, ${kim}`);
};

Peki buradaki mantık ne? Ayrıca bir "func" eklemenin ne anlamı var?

Tekrar etmek gerekirse, hala bir Fonksiyon İfadeniz var. Fonksiyon ifadesine "function" 'dan sonra "func" eklemek bu fonksiyonu Fonksiyon Tanımı haline getirmez çünkü hala bir atama operasyonu ile tanımlanmıştır.

Böyle bir isim eklemek hiçbir soruna neden olmaz.

Fonksiyon hala selamVer() şeklinde kullanılabilir:

let selamVer = function func(kim) {
  alert(`Selam, ${kim}`);
};

selamVer("Ahmet"); // Selam, Ahmet

func ismine ait iki tane özellik vardır:

  1. Bu şekilde fonksiyonun kendisine içerisinden referans vermek mümkündür.
  2. Fonksiyonun dışından erişilemez.

Örneğin, selamVer fonksiyonu eğer bir parametre olmadan çağırılırsa kendisini "Misafir" ile tekrardan çağırabilir.

let selamVer = function func(kim) {
  if (kim) {
    alert(`Selam, ${kim}`);
  } else {
    func("Misafir"); // kendisni yeniden çağırabilir.
  }
};

selamVer(); // Selam, Misafir

// Fakat aşağıdaki çalışmayacaktır:
func(); // func tanımlı değildir. ( Fonksiyonun dışından erişilemez.)

Peki neden func kullanıyoruz? Sadece selamVer kullansak olmaz mı?

Aslında çoğu durumda olur:

let selamVer = function(kim) {
  if (kim) {
    alert(`Selam, ${kim}`);
  } else {
    selamVer("Misafir");
  }
};

Buradaki problem selamVer’in değeri değişebilir. Fonksiyon diğer bir değişkene gidebilir ardından hatalar vermeye başlar.

let selamVer = function(kim) {
  if (kim) {
    alert(`Selam, ${kim}`);
  } else {
    selamVer("Misafir");
  }
};

let hosGeldin = selamVer;
selamVer = null;

hosGeldin(); //Artık selamVer çağırılamaz.

Bunun olmasının nedeni fonksiyonun selamVer’i dış ortamdan alıyor olmasıdır. Yerel bir selamVer bulunmadığından dıştaki değişken kullanılmaktadır. O anda da dışta bulunan selamVer null’dur.

Opsiyonel olarak konulan isim tam olarak Fonksiyon İfadesinin bu problemini çözer.

Bunu kullanarak kod şu şekilde düzeltilebilir:

let selamVer = function func(kim) {
  if (kim) {
    alert(`Selam, ${kim}`);
  } else {
    func("Misafir"); // Şimdi hepsi doğru şekilde çalışır.
  }
};

let hosGeldin = selamVer;
selamVer = null;

hosGeldin(); // Selam, Misafir (iç çağrı çalışır)

Şimdi çalışır, bunun nedeni "func"'in lokal fonksiyon olmasındandır. Dışarıdan alınmaz ( dışarıdan görünmez de ). Bu şekilde yazıldığında var olan fonksiyonu referans vereceği garantidir.

Dışta bulunan kod hala selamVer veya hosGeldin değişkenlerine sahiptir. Dıştaki değişkenlere bir şey olsa bile func"iç fonksiyon ismi"'dir. Kendisini gizli biçimde çağırabilir.

Fonksiyon Tanımı diye bir şey yoktur.

“içsel isim” olarak tanımlanan özellik sadece Fonksiyon İfadeleri için geçerlidir. Fonksiyon Tanımlarında çalışmaz. Fonksiyon tanımları için “içsel” bir isim ekleme yöntemi yoktur.

Bazen güvenli bir isme ihtiyaç duyulduğunda Fonksiyon Tanımı tekrardan İsimlendirilmiş Fonksiyon İfadesi şekline getirilir.

Özet

Fonksiyonlar objedir.

Özellikleri şu şekildedir:

  • name – Fonksiyon ismi. Sadece fonksiyon tanımlama da değil, atamalar ve obje özellikleri için.
  • length – Fonksiyon tanımındaki argüman sayısı, geriye kalan parametreleri ( … ) sayılmaz.

Eğer fonksiyon Fonksiyon Tanımı yöntemi ile ( ana akışta olmayan ) tanımlanmışsa ve isim taşıyorsa buna İsimlendirilmiş Fonksiyon Tanımı denir. İsim içersiinde veya recursive çağrılarda kullanılabilir.

Fonksiyonlar ayrıca başka özelliklerde taşıyabilirler. Çoğu bilinen JavaScript kütüphanesi bu özelliği ziyadesiyle kullanır.

Genelde bir “ana” fonksiyon ve bu fonksiyona bağlı birçok “yardımcı” fonksiyon tanımlarlar. Örneğin jquery $ adında bir fonksiyon oluşturur. lodash fonksiyonu _ adında bir fonksiyon oluşturu. Ardıncan _.clone, _.keyBy gibi özellikleri ekler. Dökümantasyon’u inceleyebilirsiniz. Aslında, global alanda baya temiz çalışırlar. Böylece bir kütüphane bir tane global değişken vermiş olur. Bu da isimlerin birbiriyle çakışmasını engeller.

Bundan dolayı bir fonksiyon kendince bir iş yapabilir ve özellikleri vasıtasıyla başka fonksiyonalitelere de sahip olabilir.

Görevler

önem: 5

sayacUret() kodunu değiştirerek sayacın azaltılabilir ve ayarlanabilir olmasını sağlayınız:

  • sayac() bir sonraki sayıyı döndürmelidir(eskisinde olduğu gibi)
  • sayac.set(deger) sayac degerini degere eşlemelidir.
  • sayac.azalt(deger) sayac degerini bir azaltmalıdır.

İzole edilmiş koda bakarak tamamen nasıl kullanıldığını görebilirsiniz

Not: Closure veya fonksiyon özelliği kullanabilirsiniz. Her iki şekilde yazsanız daha iyi olur tabi.

Testler ile korunaklı olan aç.

Çözüm sayac yerel değişkenini kullanmaktadır, buna ek olarak metodlar doğrudan sayac’in icerisine yazılmıştır. Aynı dış ortamı paylaştıklarından dolayı var olan sayac değişkenine erişebilirler.

function sayacUret() {
  let sayac = 0;

  function sayac() {
    return sayac++;
  }

  sayac.set = deger => sayac = deger;

  sayac.azalt = () => sayac--;

  return sayac;
}

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

önem: 2

topla adında bir fonksiyon yazın ve aşağıdaki gibi çalışsın:

topla(1)(2) == 3; // 1 + 2
topla(1)(2)(3) == 6; // 1 + 2 + 3
topla(5)(-1)(2) == 6
topla(6)(-1)(-2)(-3) == 0
topla(0)(1)(2)(3)(4)(5) == 15

Not: İlkel tipe dönüştürmek için ayrıca özel bir obje yaratmanıza gerek yok.

  1. Tümünün çalışabilir olası için topla’nın fonksiyon olması lazım.
  2. Bu fonksiyon çağrıları arasında hafızada değer tutabilmeli.
  3. Göreve göre, fonksiyon == kullanıldığında sayı olmalıdır. Fonksiyonlar obje olduğundan dolayı dönüşümleri Objelerin ilkel çevirileri bölümünde anlatıldığı gibi çevrilirler, bu objenin sayı olarak dönebilmesi için kendi metodumuzu kullanabiliriz.

Kod:

function topla(a) {

  let anlikToplam = a;

  function f(b) {
    anlikToplam += b;
    return f;
  }

  f.toString = function() {
    return anlikToplam;
  };

  return f;
}

alert( topla(1)(2) ); // 3
alert( topla(5)(-1)(2) ); // 6
alert( topla(6)(-1)(-2)(-3) ); // 0
alert( topla(0)(1)(2)(3)(4)(5) ); // 15

topla fonksiyonu sadece bir defa çalışır. f fonksiyonu döndürür.

Sonrasında her bir çağrı için f’i tekrar çağırır ve anlikToplam değerine kendisini ekler ve döndürür.

f’in son satırında recursion bulunmamakta

Recursion ( kendini çağırma ) aşağıdaki gibi görünmektedir:

function f(b) {
  anlikToplam += b;
  return f(); // <-- kendini çağırıyor
}

Bizim durumumuzda sadece çağırmadan kendisini döndürmektedir.

function f(b) {
  anlikToplam += b;
  return f; // <-- kendisni çağırmamakta, kendi değerini dönüyor.
}

f değeri bir sonraki çağrıda kullanılacaktır, ne kadar çağırılırsa o kadar kullanılır. Ne zaman ki sayı veya karakter dizisi olarak kullanılacak olursa – toString metodu anlikToplam değerini döner. Bunun yanında Symbol.toPrimitive veya valueOf da kullanılabilirdi.

Eğitim haritası