Bu bölümde fonksiyonlar daha derinlemesine incelenecektir.
İlk konu özçaÄrı olacaktır.
EÄer daha önce program yazdıysanız bir sonraki bölüme geçebilirsiniz.
Ãz çaÄrı bir programlama desenidir. Bu desen bir görevin aynı türde daha basit görevcikler haline getirilmesini saÄlar. Veya bir görev daha kolay aksiyonlara ve aynı Åekilde görevlere dönüÅtürülebildiÄinde, veya daha sonra göreceÄiniz gibi, belirli veri yapılarında kullanılabilir.
Bir fonksiyon problemi çözerken birçok farklı fonksiyonu çaÄırabilir. Bu özel durumda ise fonksiyon kendisini çaÄırır. Bu olaya özçaÄrı, recursion denir.
Ãift yönlü düÅünme
BaÅlangıçta us(x,n) adında bir fonksiyon olsun ve bu n üssü x i hesaplasın. DiÄer bir ifadeyle xâi n defa kendisiyle çarpsın.
us(2, 2) = 4
us(2, 3) = 8
us(2, 4) = 16
Bunu uygulamanın iki yolu bulunmaktadır.
-
Tekrarlı düÅünürseniz:
fordöngüsü:function us(x, n) { let sonuc = 1; // x'in x defa kendisiyle çarpımı. for(let i = 0; i < n; i++) { sonuc *= x; } return sonuc; } alert( us(2, 3) ); // 8 -
ÃzçaÄrı: iÅi daha basite indirgeyerek kendisini çaÄırsın:
function us(x, n) { if (n == 1) { return x; } else { return x * us(x, n - 1); } } alert( us(2, 3) ); // 8
Dikkat ederseniz özçaÄrı fonksiyonu aslen farklıdır.
us(x,n) çaÄrıldıÄında çalıÅtırılma iki dala ayrılır.
if n==1 = x
/
us(x, n) =
\
else = x * us(x, n - 1)
-
EÄer
n==1ise geriye kalanlar önemsizdir. Buna temel özçaÄrı denir, çünkü bu belirli bir sonucu çıktı verir:us(x,1)eÅittirx -
DiÄer türlü
us(x,n)x*us(x,n-1)Åeklinde ifade edilebilir. Matematiksel olarakxn = x * xn-1Åeklinde ifade edilebilir. Buna öztekrar basamaÄı denir. Görev daha küçük aksiyonlara (xile çarpma ) indirgenmiÅ olur. Ayrıca aynı görevi daha basit görevlerle (usâün daha küçükndeÄeri) indirgenmiÅ oldu. Bir sonraki sitep ise bunun daha basite indirgene indirgenenâin1e ulaÅmasını saÄlamaktır.
Buna us öz çaÄrı ile kendisini n==1 olana kadar çaÄırır diyebiliriz.
us(2,4)'ü hesaplayabilmek için özçaÄrı Åu adımları gerçekleÅtirir:
us(2, 4) = 2 * us(2, 3)us(2, 3) = 2 * us(2, 2)us(2, 2) = 2 * us(2, 1)us(2, 1) = 2
özçaÄrı böylece fonksiyon çaÄrılarını dah abasite indirgemiÅtir. Daha sonra daha basite ve en sonunda sonuç belirli olana kadar devam etmiÅtir.
AÅaÄıda aynı fonksiyonun ? ile tekrar yazılmıŠhali bulunmaktadır.
function us(x, n) {
return (n == 1) ? x : (x * us(x, n-1));
}
Maksimum iç içe çaÄırma sayısına özçaÄrı derinliÄi us fonksiyonunda bu nâdir.
JavaScript motorları maksimum özçaÄrı derinliÄini sınırlamaktadır. Bazı motorlarda 10000, bazılarında 100000 limiti bulunmaktadır. Bunun için otomatik optimizasyonlar bulunmaktadır. Fakat yine de her motorda desteklenmemektedir ve çok basit durumlarda kullanılır.
Bu özçaÄrı uygulamalarını limitler, fakat yine de çoÄu yerde kullanılmaktadırlar. ÃoÄu görevde özçaÄrı Åeklinde düÅünmek daha basit ve sürdürülebilir bod yazmanızı saÄlayacaktır.
ÃalıÅtırma YıÄını
Peki özçaÄrılar nasıl çalıÅır. Bunun için fonksiyonların içinde ne yaptıklarına bakmak gerekmektedir.
ÃalıÅan fonksiyon hakkında bilgi çalıÅtırma kaynaÄında tutulur.
ÃalıÅtırma KaynaÄı â Execution Context fonksiyonun çalıÅması hakkında detayları tutan dahili bir veri yapısıdır: Kontrol akıÅı nerede, o anki deÄiÅkenlerin deÄeri, this neye denk gelir ve bunun gibi detaylar dahili detaylar tutar.
Her fonksiyon çaÄrısı kendine ait çalıÅtırma kaynaÄı tutar.
EÄer bir fonksiyon içeride baÅka bir çaÄrı yaparsa Åunlar olur:
- O anki fonksiyon durur.
- Bu fonksiyon ile ilintili çalıÅma kaynaÄı çalıÅma kaynaÄı yıÄını veri yapısı Åeklinde kaydedilir.
- Dallanılan çaÄrı çalıÅtırılır.
- Bu iÅlem bittikten sonra çalıÅma kaynaÄı yıÄınından daha önceki çalıÅmakta olan yer geri alınır, böylece fonksiyon kaldıÄı yerden görevini tamamlayabilir.
AÅaÄıda us(2,3)'ün çalıÅması gösterilmiÅtir.
us(2, 3)
us(2,3) çaÄrısının baÅlangıcında, çalıÅma kaynaÄı deÄiÅkenleri x=2,n=3 olacak Åekilde tutar. ÃalıÅma Åu anda birinci satırdadır.
Bu aÅaÄıdaki gibi gösterilebilir:
- ÃalıÅma kaynaÄı: { x: 2, n: 3, birinci satırda } us(2, 3)
Ardından fonksiyon çalıÅmaya baÅlar. n==1 Åartı yanlıÅtır, bundan dolayı ikinci ifâe geçer.
function us(x, n) {
if (n == 1) {
return x;
} else {
return x * us(x, n - 1);
}
}
alert( us(2, 3) );
DeÄiÅkenler aynı fakat satır deÄiÅtir, Åimdiki kaynak Åu Åekilde:
- Kaynak: { x: 2, n: 3, 5. satırda } us(2, 3)
x*pow(x, n-1)'i hesaplayabilmek için us fonksiyonuna us(2,2) Åeklinde yeni bir çaÄrı yapılmalıdır.
us(2, 2)
Dallanma iÅleminin yapılabilmesi için JavaScriptâin öncelikle o anki çalıÅma durumunu çalıÅma kaynaÄı yıÄınına atması gerekmektedir.
Burada us fonksiyonu çaÄrılmıÅtır. Bu herhangi bir fonksiyon da olabilirdi, aralarında bu yönden hiçbir farklılık bulunmamaktadır:
- O anki kaynak yıÄının en üstüne âhatırlatılırâ
- Alt çaÄrı için yeni bir kaynak yaratılır.
- Alt çaÄrılar bittiÄinde â bir önceki kaynak yıÄından alınır ve çalıÅmasına devam eder.
AÅaÄıda pow(2,2) altçaÄrısına girildiÄinde kaynak yıÄınının durumu gösterilmektedir.
- Kaynak: { x: 2, n: 2, 1. satırda } us(2, 2)
- Kaynak: { x: 2, n: 3, 5. satırda } us(2, 3)
Ãst tarafta o anda çalıÅan kaynak ( kalın harflerle ), alt tarafta ise âhatırlatılanâ kaynak bulunmaktadır.
AltçaÄrı bittiÄinde, daha önceki kalınan kaynaktan devam etmek kolaydır. Ãünkü bu her iki deÄiÅkeni ve kaldıÄı satırı tutmaktadır. Burada âsatırâ denmesine raÄmen aslında bunun daha net bir Åey olduÄu bilinmelidir.
us(2, 1)
İÅlem tekrar ediyor: 5. satırda yeni bir altçaÄrı yapılmaktadır, argümanlar ise x=2, n=1 Åeklindedir.
Yeni çalıÅma yıÄını oluÅturur, bir önceki yıÄının üstüne itelenir. A new execution context is created, the previous one is pushed on top of the stack:
- Kaynak: { x: 2, n: 1, 1. satır } us(2, 1)
- Kaynak: { x: 2, n: 2, 5. satır } us(2, 2)
- Kaynak: { x: 2, n: 3, 5.satır } us(2, 3)
Åu anda 2 eski kaynak ve 1 tane çalıÅmakta olan kaynak bulunmaktadır us(2,1)
ÃıkıÅ
us(2,1) çalıÅırken diÄerlerinin aksine n==1 Åartı saÄlanır, bundan dolayı ilk defa birinci if çalıÅır.
function us(x, n) {
if (n == 1) {
return x;
} else {
return x * us(x, n - 1);
}
}
Daha fazla dallanan çaÄrı olmadıÄından dolayı fonksiyon sona erer ve deÄeri döner.
Fonksiyon bittiÄinden dolayı, çalıÅma kaynaÄına gerek kalmamıÅtır ve dolayısıyla hafızadan silinir. Bir önceki yıÄından alınır:
- Kaynak: { x: 2, n: 2, 5. satırda } us(2, 2)
- Kaynak: { x: 2, n: 3, 5. satırda } us(2, 3)
us(2,2) nin çalıÅması devam etti. us(2,1)'in sonucuna sahip olduÄundan x * us(x,n-1)'in sonucunu bulabilir, bu da 4âtür.
Ardından bir önceki kaynak geri yüklenir:
- Kaynak: { x: 2, n: 3, 5. satırda } us(2, 3)
İÅlemler bittiÄinde, us(2,3) = 8 sonucu alınır.
Bu durumda özçaÄrı derinliÄi 3tür
Yukarıda da görüldüÄü üzere, özçaÄrı derinliÄi yıÄındaki kaynak sayısı demektir. Bu drumda nâin üssü deÄiÅtirildiÄinde daha fazla hafıza kullanacaktır.
Döngü bazlı algoritma daha az hafıza kullanacaktır:
function us(x, n) {
let sonuc = 1;
for(let i = 0; i < n; i++) {
sonuc *= x;
}
return sonuc;
}
Tekrar eden us fonksiyonu i ve sonuc kaynaÄını kullanır ve sürekli bunları deÄiÅtirir. Hafıza gereksinimleri oldukça azdır ve bu hafıza büyüklüÄü nâe baÄlı deÄildir.
Tüm özçaÄrılar döngü olarak yazılabilir. Döngü versiyonu daha az kaynak gerektirecektir
⦠Bazen yeniden yazmak çok kolay deÄildir, özellikle fonksiyon alt çaÄrılarda özçaÄrı kullanıyorsa, bu çaÄrılar sonucunda daha karmaÅık dallanmalar oluyor ise optimizasyon deÄmeyebilir.
ÃzçaÄrı fonksiyonun daha kısa kod ile yazılmasını saÄlar, ayrıca anlaÅılmayı da kolaylaÅtırır. Optimizasyon her yerde gerekli deÄildir, genelde iyi kod gereklidir, bunun için kullanılır.
ÃzçaÄrı AkıÅı
ÃzçaÄrıların kullanılabileceÄi diÄer bir uygulama özçaÄrı akıÅıdır.
Bir firma hayal edin. ÃalıÅanların yapısı obje olarak Åu Åekilde tanımlanabilir:
let firma = {
satis: [{
adi: 'Ahmet',
maasi: 1000
}, {
adi: 'Mehmet',
salary: 150
}],
gelistirme: {
siteler: [{
adi: 'Mustafa',
ucret: 200
}, {
adi: 'Mazlum',
ucret: 50
}],
dahili: [{
adi: 'Zafer',
ucret: 1300
}]
}
};
DiÄer bir deyiÅle bu firmanın departmanları bulunmaktadır.
-
Bir departman çalıÅanlar dizilerinden oluÅabilir. ÃreÄin
satisdepartmanı 2 tane çalıÅana sahiptir: Ahmet ve Mehmet. -
Veya bazen departmanlar alt departmanlara ayrılabilirler. ÃrneÄin
gelistirmedepartmanısitelervedahiliolmak üzere ikiye ayrılmıÅtır. Her bir alt departmanın kendine ait çalıÅanları vardır. -
Bunun yanında departmanların büyüyüp alt departmanlara ayrılması da mümkündür.
ÃrneÄin
sitelerdepartmanı ileride iki ayrı takımasiteAvesiteBÅeklinde ayrılabilirler. Ve yine potansiyele göre ileride bu takımlar da alt takımlara ayrılabilirler.
Ãyle bir fonksiyon olsun ki tüm çalıÅanların maaÅlarının toplamını dönsün. Bu nasıl yapılır?
Döngü yaklaÅımı kolay deÄildir, çünkü yapı kolay deÄildir. Ãnce firma için bir for döngüsü kullanıldıÄını ve bununla ilk seviye departmanları bulduÄunuzu varsayın. Sonrasında bunun içine bir döngü daha yapıp sitelerâi bulmanız gerekir. Ayrıca ilerisi için bir tane daha for döngüsü yapmanız lazım ve belki yine onun içerisine de bir döngü koymanız lazım. 3. basamakta mı 4. basamakta mı durmalı? EÄer ileride bu yapı sadece bir seviyeye indirilirse kodda karmaÅıklık meydana gelir.
ÃzçaÄrı yaklaÅımıyla.
Fonksiyon toplanacak departmanı aldıÄında iki muhtemel durum mevcuttur:
- Bu âbasitâ bir departman olabilir içerisinde çalıÅanlar bulunur â sonra bunların maaÅları basit bir döngüyle toplanabilir.
- Veya
Nalt departmana sahip obje olabilir â öyleyseNdefa özçaÄrı yapıp her bir alt departmanın toplamının sonucunu döndürülür.
(1) özçaÄrının temelidir.
(2) ÃzçaÄrının tekrar eden adımlarıdır. KarmaÅık görev daha küçük departman görevlerine ayrılır. Sonrasında yine ayrılabilir fakat en sonunda (1)'e eriÅecektir.
Algoritma kodunu okumak oldukça kolaydır:
let firma = {
satis: [{
adi: 'Ahmet',
maasi: 1000
}, {
adi: 'Mehmet',
salary: 150
}],
gelistirme: {
siteler: [{
adi: 'Mustafa',
ucret: 200
}, {
adi: 'Mazlum',
ucret: 50
}],
dahili: [{
adi: 'Zafer',
ucret: 1300
}]
}
};
// İÅi yapan fonksiyon
function maaslariTopla(firma) {
if (Array.isArray(firma)) { // (1). durum
return firma.reduce((onceki, suanki) => onceki + suanki.salary, 0); // diziyi topla
} else { // (2.) durum
let toplam = 0;
for(let altDep of Object.values(altDep)) {
sum += maaslariTopla(altDep); // özçaÄrı ile alt departmanların çaÄrılması, bunu sum ile topla.
}
return sum;
}
}
alert(maaslariTopla(firma)); // 2700
Kod oldukça kısa ve anlaması kolay(umarım). Burada özçaÄrının gücünden bahsetmek mümkün, her seviye alt departman için çalıÅacaktır.
AÅaÄıda ise bu çaÄrının diyagramı bulunmaktadır.
Prensip basitçe Åu Åekilde açıklanabilir: Obje için {...} altçaÄrıları yapılır, [...] ise özçaÄrı aÄacının âyapraklarıdırâ, anında sonucu dönerler.
Kodun akıllı özellikler kullandıÄına dikkat edin, bunlar daha önceki kolarda iÅlenmiÅti:
arr.reducemetodu Dizi Metodları bölümünde bir dizinin toplamını almak için kullanılmıÅtı.for(val of Object.values(obj))objenin deÄerlerini dönmek için kullanılmıÅtı:Object.valuesobjenin deÄerlerini dizi olarak döner.
ÃzçaÄrı yapıları
ÃzçaÄrı yapıları, kendini bazı bölümlerde tekrar eden veri yapılarıdır.
Ãrnekte kullanılan firmalar objesi bu yapıyı kullanmaktadır.
Bir departman
- Dizi veya çalıÅanlardan oluÅur.
- Veya departmanlardan oluÅur.
Web-geliÅtiricileri için daha bilinen bir örneÄi: HTML ve XML dökümanlarıdır.
HTML dökümanında, HTML-tagâı Åunları içerebilir:
- Metinler
- HTML-yorumları
- DiÄer HTML-tagları ( bunlar da yine metinler, yorumlar ve diÄer tagları içerebilir)
Bu da yine özçaÄrı yapısıdır.
Daha iyi anlaÅılması için âLinked listâ yapısı üzerinden gitmek gerekir. Bu bazı durumlarda dizilere alternatif olarak kullanılabilir.
Linked list
Diyelim objelerin sıralı Åekilde liste halinde tutmak istiyorsunuz.
Diziler ile aÅaÄıdaki gibi yapılabilir:
let arr = [obj1, obj2, obj3];
⦠Fakat diziler âeleman silmeâ, âeleman ekleâ gibi olaylar için çok iÅlem yaparlar. ÃrneÄin arr.unshift(ob) iÅlemi tüm elemanları yeni eleman için tekrardan sıraya dizer, eÄer dizi büyükse bu zaman alır. Aynısı arr.shift() için de geçerlidir.
Tekrardan numaralama gerektirmeyen arr.push/pop metodları kullanılabilir. Bunlar da dizinin sonuna ekler veya çıkarır. Ãok elemanlı dizilerde bu iÅlemlerin yavaÅ olacaÄı söylenebilir.
Alternatif olarak, eÄer hızlı bir Åekilde, silme/yerleÅtirme istenirse diÄer bir veri yapısı olan linked list kullanılabilir.
linked list elemanı özçaÄrı biçimde aÅaÄıdaki obje gibi tanımlanır:
deger.sonrakisonraki linked list elemanıânı tenımlar, sonuncuysanulldöner.
ÃrneÄin:
let list = {
deger: 1,
sonraki: {
deger: 2,
sonraki: {
deger: 3,
sonraki: {
deger: 4,
sonraki: null
}
}
}
};
Bu listenin grafiksel gösterimi Åu Åekildedir:
Bu yapıyı yaratmanın alternatif yolu Åu Åekildedir:
let list = { deger: 1 };
list.sonraki = { deger: 2 };
list.sonraki.sonraki = { deger: 3 };
list.sonraki.sonraki.sonraki = { deger: 4 };
Burada görüldüÄü üzere her obje degere sahiptir ve komÅusu olan sonrakini gösterir. list deÄiÅkeni bu zincirin ilk halkasıdır, sonrasında sonraki pointerâını takip eder.
Liste kolayca birçok parçaya bölünebilir ve sonradan tek bir yapı haline getirilebilir:
let ikinciList = list.sonraki.sonraki;
list.sonraki.sonraki = null;
BirleÅtirme:
list.sonraki.sonraki = ikinciList;
Ve istenildiÄi gibi elemanlar bir yerden silinebilir veya eklenebilir.
ÃrneÄin yeni bir deÄer ekleneceÄi zaman, listenin baÅlangıcının güncellenmesi gerekir:
let list = { deger: 1 };
list.sonraki = { deger: 2 };
list.sonraki.sonraki = { deger: 3 };
list.sonraki.sonraki.sonraki = { deger: 4 };
// Yeni bir deÄer ekleneceÄi zaman
list = { deger: "yeni eleman", sonraki: list };
Yine ortalardan bir yerden veri silineceÄi zaman sonrakiânin bir öncekine getirilmesi gerekri.
list.sonraki = list.sonraki.sonraki;
list.sonrakiânin deÄeri 1âden 2âye geçirildi. 1 deÄeri artık zincirden çıkarıldı. EÄer bu deÄer baÅka bir yerde tutulmuyor ise, bu deÄer ileride otomatik olarak hafızadan silinecektir.
Diziler gibi çok büyük sayida tekrar numaralama bulunmamaktadır, ve kolayca istenilen eleman istenilen yere koyulur.
Her zaman List diziden daha iyidir denemez. Ãyle olsaydı herkes dizi yerine List kullanırdı.
En büyük handikapı Listâte istenilen elemana kolayca eriÅim saÄlanamaz. Dizilerde bu oldukça kolaydır: dizi[n] doÄrudan referans verir. Fakat dizilerde ilk elemandan itibaren sonraki Åeklinde N defa gitmek gerekir.
Fakat çoÄu zaman böyle bir iÅleme ihtiyaç duymayız. ÃrneÄin bir kuyruk ihtiyacı olduÄunda hatta deque ihtiyacı olduÄunda hızlı bir Åekilde baÅtan veya sondan eleman eklenip silinmesi gerekir.
Bazen kuyruk adında bir deÄiÅken eklenerek ( yeni eleman eklendiÄinde/çıkarıldıÄında ) listenin son elemanı takip edilebilir. Büyük dizilerde listeye göre hız oldukça fazladır.
Ãzet
Tanımlar:
-
Ãz ÃaÄrı kendi kendini çaÄırma fonksiyonu demektir. Böyle fonksiyonlar belirli yapıdaki görevlerin çözülmesini saÄlar.
Fonksiyon kendisini çaÄırdıÄında buna öztekrar adımı denir. temel ise öz çaÄrı fonksiyonun argümanının tekrar öz çaÄrı yapılamayacak kadar basite indirgenmesi olayıdır.
-
ÃzçaÄrı yapısı kendisini tekrar kullanarak tanımlanan veri yapılarıdır.
ÃrneÄin, linked list objenin listeyi referans veren bir veri yapısı olarak tanımlanabilir.
list = { deger, sonraki -> list }HTML elemanlarının aÄacı veya departman aÄacı gibi yapılar özçaÄrı yapısıdır: Bunların dalları ve dallarının yine dalları bulunmaktadır.
ÃzçaÄrı fonksiyonları
maaslariToplafonksiyonunda olduÄu gibi elemanların üzerinden geçer.
Her özçaÄrı fonksiyonu tekrarlı Åekile getirilebilir. Bazen optimize etmek için kullanılabilir. Fakat çoÄu görev için özçaÄrı çözümleri yeteri kadar hızlı ve yazması kolaydır.
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)