JavaScript Yapıcı Fonksiyonlar (Constructor)
Terim olarak ismi ile Generator (Jeneratör, Yapıcı, Takım fonksiyonlar olarak adlandırılır), tekrar tekrar girilip çıkılabilen fonksiyonlardır. Bu yapıları sebebiyle kendi içlerindeki durumları korurlar. Yani normal bir fonksiyondan farklı olarak çalışmaya kaldığı yerden devam ederler. Bu bölümde de yapıcı fonksiyonların yapılarına, nasıl kullanıldıklarına ve ne gibi durumlarda çözüm üreteceklerine bakacağız.
Yapıcı Fonksiyon Tanımlama ve Yapısı
Normal fonksiyonlar yalnızca bir, tek bir değer veya hiçbir şey döndürmez. Yapıcı fonksiyonlar, çağrıldıklarında birbiri ardına birden çok değer döndürebilir. Yinelenebilir özellikleri sorunsuz çalışırlar ve kolaylıkla veri akışları oluşturmayı sağlarlar. İstenildiklerinde fonksiyonun işlevi yürütmesi askıya alınabilir ve herhangi bir zamanda devam ettirilebilir.
Yapıcı fonksiyon tanımlanırken function* deyimi kullanılır. Dikkat edin, function deyimi ile birlikte bitişik olarak * (yıldız, çarpma) operatörü kullanıldı.
function* siraOlustur() {
yield 1;
yield 2;
return 3;
}
Yukarıda, basitçe siraOlustur adında bir Generator oluşturduk. Burada farklı olarak fonksiyon içinde yield deyimi gördük, bunu da bir sonraki konumuzda değineceğiz.
yield ile Yapıcı Fonksiyonların Kullanımı
Yapıcı fonksiyonlar, normal olanlardan farklı davranır. Böyle bir fonksiyon çağrıldığında, kodu çalıştırmaz. Bunun yerine, yürütmeyi yönetmek için generator nesne döndürür.
function* siraOlustur() {
yield 1;
yield 2;
return 3;
}
let generator = siraOlustur ();
console.log(generator);
Bir jeneratörün ana metodu next() yöntemidir. Çağrıldığında, en yakın, yani sıradaki yield <değer> ifadesine kadar yürütmeyi çalıştırır (değer atlanabilir, daha sonra undefined’tir). Ardından fonksiyon yürütme duraklar ve verilen değer dış koda return edilir.
Normal fonksiyonlarda olduğu gibi bir yapıcı fonksiyon bir değişkende de tutulabilir.
function* siraOlustur() {
//...
};
const generator = siraOlustur();
next() metodunun sonucu her zaman 2 özelliğe sahip bir nesnedir: Bunlar sırasıyla değer ve durumdur.
Değer (value): Verilen değer.
Durum (done): Fonksiyon kodu başarılı bir şekilde tamamlandıysa true, aksi halde false olur.
Az önce yazdığımız yapıcı fonksiyonu next() metodunu ile çalıştıralım.
function* siraOlustur() {
yield 1;
yield 2;
return 3;
}let generator = siraOlustur();const value = () => {
return generator.next().value;
}console.log(value()); //1
console.log(value()); //2
console.log(value()); //3
console.log(value()); //undefined
Yapıcı fonksiyonumuzu next().value ile kullanarak value adlı bir fonksiyonda return ediyoruz. Yapıcı fonksiyon her çağrıldığında yield değeri geri dönecektir. Konsolda value fonksiyonumuzu 4 defa yazdırdık. Sırasıyla 1, 2 ve 3 değerleri geldi. Yapıcı fonksiyon her çağrıldığında yield sırası bir ilerledi ve return da default değeri gönderdi. Ancak 4. çağrıda döndürülecek bir değer olmadığı için undefined geri döndürüldü.
Başka bir fonksiyonda da sonsuz değer döndürmesini sağlayalım.
function* generateGuestName() {
let index = 0;
while (true)
yield "Misafir#" + index++;
}let guestName = generateGuestName();console.log(guestName.next().value); // Misafir#0
console.log(guestName.next().value); // Misafir#1
console.log(guestName.next().value); // Misafir#2
console.log(guestName.next().value); // Misafir#3
// ...
Bu örneğimizde de herhangi bir yield sınırlaması yok. Fonksiyonumuzda while(true) ile bir sonsuz döngü oluşturduk. Normalde sonsuz döngüler programın kırılmasına sebep olur, ancak yapıcı fonksiyonlarda yalnızda bir defa çalışmayla sınırlandıkları için bir sorun olmuyor. Döngü içinde de her seferinde index adlı değer bir artırılıp bir metin ile birleştirilerek yield ile geri döndürülüyor. Örnekte, otelimize gelen misafirler için sırasıyla isim tanımlayacak bir fonksiyon oluşturduk. Her çağrıldığında “Misafir#0” olarak başlayıp ilerleterek isim üretecek.
Örneğimizi sadece value değil de, value ve done değerlerini birer Object olarak alalım…
console.log(guestName.next());
Görüldüğü üzere Object içinde değerlerimiz geldi. Peki, done neden false olarak geldi? Sonuçta .next() ile aldığımız değer gelmişti…
Done özelliği ile gelen true ve false değerinin anlamı, o an çalıştırılan yield’ten sonra başka bir yield olup olmadığıdır. Eğer bir sonraki çağrıda kullanılacak yield varsa false (yani süreç hala tamamlanmadı, yeni yield’ler var), eğer başka bir yield yoksa true (yani süreç tamamlandı, bundan sonra istek yaparsan undefined döner) döndürür.
function* max2UserForGame() {
yield "Oyuncu 1",
yield "Oyuncu 2"
}const userName = max2UserForGame();console.log(userName.next());
console.log(userName.next());
console.log(userName.next());
İlk 2 işlem için yield tanımlı iken, 3. iterasyonda sonuç gelmiyor. Değer olarak undefined, durum için de true sonucu geldi. Eğer undefined yerine başka anlamlı bir değer döndürmek istersek, ilk örnekte olduğu gibi return kullanmamız gerekir.
Yapıcı Fonksiyonlarda İşlem Sınırlandırma
Yapıcı fonksiyonları sonsuz sayıda da çağırabiliriz, tek sefer olarak da, sadece yield tanımlama sıralarıyla da… Ancak bazı durumlarda yapıcı fonksiyonları sadece belli bir limite göre kullanmamız gerekebilir. Mesela for döngüsü kullanarak belli bir tekrar ile sıralı işlem yaptıralım.
Örneğimizde fibonacci sayılarını belli bir tekrara göre getirelim.
function* fibonacciGenerator() {
[a, b] = [0, 1]
while (true) {
yield a;
[a, b] = [b, a + b]
}
}const fibonacci = fibonacciGenerator();let fibonacciSeries = [];for (let i = 0; i < 10; i++) {
fibonacciSeries.push(fibonacci.next().value);
}console.log(fibonacciSeries.toString()); //0,1,1,2,3,5,8,13,21,34
Örneğimizde 10 tekrarla fibonacci sayısı üretip bir Array’e ekleyerek ilerledik. Sonunda da bu dizi değerlerini yazdırdık.
Yapıcı fonksiyonu incelediğimizde de ilk çağrıldığında a ve b adlarında birer değişken oluşturup 0 ve 1 değerlerini verdik. Burada kısa yolla değişken tanımlama örneği görülmektedir. İşlem tekrarı olarak da bir sonsuz döngü oluşturduk ve her döngüde a değerine b değerini, b değerine de a+b değerini atayarak ilerledik.
Yapıcı Fonksiyonlarda Parametre Kullanımı
Yapıcı fonksiyonlarla çalışırken parametre de kullanabiliriz. Bunun için .next() metodu içinde parametre aktarımı yapılabilir.
Bir önceki konumuzda gördüğümüz fibonacci sayısı üretme örneğini geliştirerek devam edelim. Bu sefer döngüde 10 değil de, while döngüsünde 10’a kadar olan tüm fibonacci sayılarını görelim. Bunun için de for döngüsü kullanmayalım, doğrudan yapıcı fonksiyona 10 değerini parametre olarak gönderelim.
function* fibonacciGenerator() {
[a, b, limit] = [0, 1, yield] while (a <= limit) {
yield a;
[a, b] = [b, a + b]
}
}const fibonacci = fibonacciGenerator();let fibonacciLimit = 10;console.log(fibonacci.next(fibonacciLimit)); //undefined,false
console.log(fibonacci.next(fibonacciLimit)); //0
console.log(fibonacci.next(fibonacciLimit)); //1
console.log(fibonacci.next(fibonacciLimit)); //1
console.log(fibonacci.next(fibonacciLimit)); //2
console.log(fibonacci.next(fibonacciLimit)); //3
console.log(fibonacci.next(fibonacciLimit)); //5
console.log(fibonacci.next(fibonacciLimit)); //8
console.log(fibonacci.next(fibonacciLimit)); //undefined,true
console.log(fibonacci.next(fibonacciLimit)); //undefined,true
Yapılandırıcı fonksiyona parametre göndermek için .next() metoduna değer verilir. Bu değer herhangi bir veri tipinde olabilir. Örneğimizde Integer bir değer gönderdik. Fonksiyonda ise herhangi bir parametre ile bu değeri karşılamadık. Ancak fonksiyonun içinde ilk satırda atama yaparken gönderilen değeri yield ile yakalayıp doğrudan limit değişkenine aktardık. Yani yapılandırıcı fonksiyonlara değer gönderildiğinde, fonksiyon içinde yield ile o parametreyi yakalıyoruz. Sonrasında da while döngüsünde a değeri limit değerinden küçük veya eşitse işlemin devam ettirilmesini sağladık.
Konsol sonuçlarını incelediğimizde ilk aşamada a değeri olmadığı için undefined, false geldi. Değer yoksa undefined, işlem devam ediyorsa false gelir. Şu an koşulumuz hala geçerli olduğu için false geldi. Sonra da sırasıyla fibonacci sayılarımız döndü. Ancak üretilen a değeri, yani sıradaki üretilen fibonacci sayısı limit değerinden büyük olduğu için döngüye girilmedi ve undefined, true sonuçlarını verdi. Yani değer yok, işlem tamamlandı.
Yapıcı fonksiyona hem parametre gönderip hem de göndermeyebiliriz de… Bu gibi olasılıklara karşı da default bir yield değeri dönmemizde fayda var. Örneğimizde değişkenleri tanımlarken aşağıdaki gibi default yield değerini verebiliriz.
[a, b, limit] = [0, 1, yield 100]
Yani daha da sadeleştirmek istersek sadece limit değişkeni için;
let limit = yield 100;
Nu şekilde limit değeri eğer parametre olarak gelmezse 100 değeri atanacaktır.
Örneğimizi yeniden düzenleyelim…
function* fibonacciGenerator() {
[a, b, limit] = [0, 1, yield 100]
console.log("limit",limit);
while (a <= limit) {
yield a;
[a, b] = [b, a + b]
}
}const fibonacci = fibonacciGenerator();let fibonacciLimit = 10;console.log(fibonacci.next()); //100,falseconsole.log(fibonacci.next(fibonacciLimit)); //undefined,false
console.log(fibonacci.next(fibonacciLimit)); //0
console.log(fibonacci.next(fibonacciLimit)); //1
console.log(fibonacci.next(fibonacciLimit)); //1
...
Yapıcı fonksiyonumuzu ilk çağırdığımızda parametresiz olarak kullandık. Bu nedenle varsayılan olarak 100 değerini tanımladık. Sonrasında da parametreli olarak çağırıldığı için yeni değeri alıyor. Ancak bu değişim diğer a ve b değerlerini etkilemiyor.
Konsoldaki çıktıyı incelerseniz yapılandırıcı fonksiyonun içinde console.log() mesajı olmasına rağmen sadece bir defa konsol mesajı yazdırıldı. Normal fonksiyon olsaydı her çağrıldığında tekrar tekrar konsolda yazdırılması gerekirdi.
yield* ile Başka Yapıcı Fonksiyonlarla Birleştirme
Bir yapıcı fonksiyonu, başka bir yapıcı fonksiyonla beraber kullanabiliriz. Terim olarak bir yapıcı fonksiyonun başka bir yapıcı fonksiyona gömülmesi (embed) tekniğidir.
Bir başka yapıcı fonksiyon çağırmak için yield* operatörü kullanılır. Burada diğer yapıcı fonksiyonun da kendi içindeki yield sırası bitene kadar aynı satırda kalınması sağlanır. Basit bir örnekle anlamaya çalışalım…
Örneğimizde bir otelin tüm katlarını ve bu katlardaki odaların isimlerini üreten bir yöntem geliştirelim.
function* room(floorName) {
yield floorName + "A";
yield floorName + "B";
return;
}function* floor() {
yield "Lobi";
yield* room(1);
yield* room(2);
yield* room(3);
yield "Teras";
}const generateRoomName = floor();console.log(generateRoomName.next());
console.log(generateRoomName.next());
console.log(generateRoomName.next());
console.log(generateRoomName.next());
console.log(generateRoomName.next());
console.log(generateRoomName.next());
console.log(generateRoomName.next());
console.log(generateRoomName.next());
console.log(generateRoomName.next());
Örneğimizde floor ve room adlarında iki adet yapıcı fonksiyon var. Ana yapıcı fonksiyon floor, gömülen ise room fonksiyonudur. Katları tanımlayan floor fonksiyonunda giriş katı için direkt Lobi değerini verdik. Sonrasında da otelde 3 kat olduğunu, her katta da A ve B olmak üzere ikişer oda olduğunu düşünelim. Sırasıyla alttan başlayarak 1A, 1B, 2A, 2B, 3A ve 3B katlarını iki yapıcı fonksiyonu birleştirerek ürettik. Son olarak da çatı katını tanımlamak için Teras değerini döndürdük.
Yapıcı fonksiyonların birer Array veya Object gibi sıralanabilir yapılar olduğunu fark etmiş olmalısınız. Yani forEach, for, for of gibi döngülerle nesneler arasında dönebildiğimiz gibi yield* deyimi ile de yapıcı fonksiyonlarda iterasyon kullanmış oluyoruz. Aynı mantıkla bir dizi nesnesinde de yield* ile sıralı işlem yapılabilir. Az önce vermiş olduğumuz örneği farklı bir yöntemle yeniden yazalım…
const hotelInfo = {
floors: 3,
rooms: ["A", "B"]
}function* generateRoomName() { floors = arguments[0].floors;
rooms = arguments[0].rooms; yield "Lobi";
for(let i = 1; i <= floors; i++){
for(let room of rooms) {
yield i + room;
}
}
yield "Teras";
}const roomName = generateRoomName(hotelInfo);console.log(roomName.next());
console.log(roomName.next());
//...
Bu örneğimizde ise hotelInfo adında bir Object oluşturup otelin kat ve oda bilgilerini tanımladık. Bu bilgiyi de generateRoomName isimli yapıcı fonksiyonumuzu çağırdığımız yerde parametre olarak verdik ve roomName isimli iterasyona aktardık. Böylece yapıcı fonksiyona JSON verisi geçti.
generateRoomName yapıcı fonksiyonunda da arguments ile gelen parametre değerini aldık. Burada arguments ismi bir kalıptır, değişmez. Yani gelen parametre değerleri arkaplanda yapıcı fonksiyon tarafından otomatik olarak arguments değişkenine aktarılır. Aktardığımız değerleri de alıp floors ve rooms adlı değişkenlere aktardık. Sonrasında da iç içe for ve for of döngüleri ile ürettiğimiz oda isimlerini geri döndürdük. Sonuç, bir önceki örnekte olduğu gibi aynı olacaktır.
yield ile for of Döngüsü ve Rest Operatörü
Bir yapılandırıcı fonksiyonun ne kadar değer döndürebileceğini bilmemiz bazen olanaksız olabilir. Örneklerimizde sırasıyla tek tek .next() ile undefined değeri alana kadar değer çağırıyorduk, ancak bu durum pek de kullanışlı değildir. Bu nedenle bu işlemi bir döngüye alıp, sonuçları da bir dizide toplamak daha mantıklı olacaktır.
Daha önce yapmış olduğumuz otelin odalarını üreten yapılandırıcı fonksiyonumuzu biraz daha geliştirelim. Tüm üretilen oda isimlerini bir dizide toplayalım… Bu sefer yine farklı bir yazım sistemi ile süslü parantezlerden kurtularak yazalı (tavsiye etmem, ama pratik olması açısından gösteriyorum).
function* generateRoomName() {
floors = arguments[0].floors;
rooms = arguments[0].rooms;
yield "Lobi";
for(let i = 1; i <= floors; i++)
for(let room of rooms)
yield i + room;
yield "Teras";
}const roomName = generateRoomName( { floors: 3, rooms: ["A", "B"] } );const generatedRoomNames = [];for(currentRoomName of roomName)
generatedRoomNames.push(currentRoomName);console.log(generatedRoomNames.toString());
//Lobi,1A,1B,2A,2B,3A,3B,Teras