JavaScript fonksiyonlar ile uÄraÅırken inanılmaz derecede esneklik saÄlamaktadır. Fonksiyonlar baÅka fonksiyonlara gönderilebilir, obje olarak kullanılabilir. Åimdi ise bunların nasıl iletileceÄi ve nasıl dekore edileceÄinden bahsedilecektir;
Saydam Saklama
Diyelim ki slow(x) diye yoÄun iÅlemci gücüne ihtiyaç duyan bir fonksiyonunuz olsun, buna raÄmen sonucları beklediÄiniz Åekilde vermekte.
EÄer bu fonksiyon sık sık çaÄırılıyor ise farklı xâler için sonucu saklamak bizi tekrar hesaplamadan kurtarabilir.
Fakat bunu slow() fonksiyonunun içine yazmak yerine yeni bir wrapper yazmak daha iyi olacaktır. GöreceÄiniz üzere size oldukça fazla yardımı olacaktır.
Kod aÅaÄıdaki gibidir:
function slow(x) {
// burada baya yoÄun iÅlemci gücüne ihtiyaç duyan iÅler yapılmaktadır.
alert(`${x} ile çaÄırıldı`);
return x;
}
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) { // eÄer sonuç map içerisinde ise
return cache.get(x); // deÄeri gönder
}
let result = func(x); // aksi halde hesap yap
cache.set(x, result); // sonra sonucu sakla
return result;
};
}
slow = cachingDecorator(slow);
alert( slow(1) ); // slow(1) saklandı
alert( "Tekrar: " + slow(1) ); // aynısı döndü
alert( slow(2) ); // slow(2) saklandı
alert( "Tekrar: " + slow(2) ); // bir önceki ile aynısı döndü.
Yuarkıdaki kodda cachingDecorator bir dekoratörâdür: DiÄer bir fonksiyonu alan ve bunun davranıÅını deÄiÅtiren özel bir fonksiyon.
Aslında her bir fonksiyon için cachingDecorator çaÄrılabilir ve o da saklama mekanizmasını kullanır. Harika, bu Åekilde ihtiyacı olacak birçok fonksiyonumuz olabilir. Tek yapmamız gereken bu fonksiyonlara cachingDecorator uygulamak.
Saklama olayını ana fonksiyonldan ayırarak aslında daha temiz bir yapıya da geçmiŠolduk.
Detayına inmeye baÅlayabiliriz.
cachingDecorator(func) bir çeÅit âwrapper(saklayıcı)â'dır. Bu iÅlem func(x) i âsaklamaâ iÅine yarar.
GördüÄünüz gibi, saklayıcı func(x)'ı olduÄu gibi dönderir. Saklayıcının dıÅındaki slow olan fonksiyon hala aynı Åekilde çalıÅır. Aslında davranıÅın üstüne sadece saklama(caching) mekanizması gelmiÅtir.
Ãzetlersek, ayrı bir cachingDecorator kullanmanın faydaları Åu Åekildedir:
cachingDecoratortekrar kullanılabilir. BaÅka bir fonksiyona da uygulanabilir.- Saklama(caching) mantıÄı ayrılmıÅtır böylece
slowkodun içine daha fazla kod yazıp karıÅması önlenmiÅtir. - İhtiyaç halinde birden fazla dekoratör birlikte kullanılabilir.
Kaynak için âfunc.allâ kullanmak.
Yukarıda bahsettiÄimiz saklama dekoratörü obje metodları ile çalıÅmak için müsait deÄildir.
ÃrneÄin aÅaÄıdaki kodda user.format() dekorasyondan sonra çalıÅmayı durdurur:
// worker.slow sakla yapılacaktır.
let worker = {
someMethod() {
return 1;
},
slow(x) {
// burada çok zorlu bir görev olabilir.
alert("Called with " + x);
return x * this.someMethod(); // (*)
}
};
// eskisiyle aynı kod
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func(x); // (**)
cache.set(x, result);
return result;
};
}
alert( worker.slow(1) ); // orjinal metod çalıÅmakta
worker.slow = cachingDecorator(worker.slow); // Åimdi saklamaya alındı.
alert( worker.slow(2) ); // Whoops! Error: Ãzellik okunamamaktadır. `someMethod` tanımsız.
(*) satırında hata olur this.someMethodâa eriÅmeye çalıÅır fakat baÅırılı olamaz. Nedeni ne olabilir ?
Sebebi (**) satırında orjinal func(x) çaÄırılmıÅtır. Bu Åekilde çaÄırıldıÄında, fonksiyon this = undefined alır.
AÅaÄıdaki kod çalıÅtırılırsa da aynısı görülebilir:
let func = worker.slow;
func(2);
Saklayıcı çaÄrıyı gerçek çalıÅacak metoda gönderir. Fakat this olmadıÄından dolayı hata alır.
Bunu düzeltmek için.
Ãzel bir metod bulunmaktadır func.call(context, â¦args) thisâi belirterek doÄrudan fonksiyonu çaÄırmaya yarar.
Yazımı aÅaÄıdaki gibidir:
func.call(context, arg1, arg2, ...)
İlk argüman thisâdir diÄerleri ise fonksiyon için gerekli argümanlardır.
Kullanımı Åu Åekildedir:
func(1, 2, 3);
func.call(obj, 1, 2, 3)
Her ikisi de aslında func fonksiyonlarını 1, 2, 3 argümanları ile çaÄırır tek fark func.call fonksiyonunda thisde gönderilir.
ÃrneÄin, aÅaÄıdaki kod sayHi metodunu iki farklı objeye deÄer atayarak çaÄırır. Birinci satırda this=user ikinci satırda ise this=admin deÄeri atanarak bu çaÄrı gerçekleÅtirilir.
function sayHi() {
alert(this.name);
}
let user = { name: "John" };
let admin = { name: "Admin" };
// farklı objeler "this" objesi olarak gönderilebilir.
sayHi.call( user ); // John
sayHi.call( admin ); // Admin
Burada say metodunu çaÄırarak ne söyleneceÄini gönderiyoruz:
function say(phrase) {
alert(this.name + ': ' + phrase);
}
let user = { name: "John" };
// user `this` olmakta ve `phrase` ilk argüman olmaktadır.
say.call( user, "Hello" ); // John: Hello
Bizim durumumuzda saklayıcı içinde call kullanarak içeriÄi orijinal fonksiyona aktarabiliriz:
let worker = {
someMethod() {
return 1;
},
slow(x) {
alert(x + "ile çaÄırıldı");
return x * this.someMethod(); // (*)
}
};
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func.call(this, x); // "this" is passed correctly now
cache.set(x, result);
return result;
};
}
worker.slow = cachingDecorator(worker.slow); // now make it caching
alert( worker.slow(2) ); // çalıÅır
alert( worker.slow(2) ); // orjinali deÄilde hafızadaki çalıÅır.
Åimdi her Åey beklendiÄi gibi çalıÅıyor.
Daha açıklayıcı olması için thisâin nasıl ilerlediÄini inceleyebiliriz:
- Dekorasyon iÅleminden sonra
worker.slowartıkfunction(x){ ...}halini almıÅtır. - Ãyleyse
worker.slow(2)çalıÅtırıldıÄında saklayıcı2vethis=worker( noktadan önceki obje ) argümanlarını alır. - Saklayıcı(wrapper) içinde sonucun henüz belleÄe alınmadıÄını varsayarsak
func.call(this,x)o ankithis(=worker) ve ('=2`) deÄerini orjinal metoda gönderir.
âfunc.applyâ ile çoklu argüman kullanımı
cachingDecorator daha evrensel yapmak için ne deÄiÅiklikler yapmalıdır?
let worker = {
slow(min, max) {
return min + max; // CPU'ya çok yük bindiren bir iÅlem.
}
};
// aynı argüman ile çaÄırılmalıdır.
worker.slow = cachingDecorator(worker.slow);
Burada çözmemiz gereken iki problem bul
İlki min ve max deÄerlerinin bu bellek haritasında anahtar olarak nasıl tutulacaÄı. Ãnceki konuda tek x argümanı için cache.set(x,result) Åeklinde sonucu belleÄe kaydetmiÅ ve sonra cache.get(x) Åeklinde almıÅtık. Fakat Åimdi sonucu argümanların birleÅimi Åeklinde hatırlamak gerekmektedir. Normalde Map anahtarı tek deÄer olarak almaktadır.
Bu sorunun çözümü için bazı çözümler Åu Åekildedir:
- Map-benzeri bir yapı kurarak birkaç anahtarı kullanabilen bir veri yapısı oluÅturmak.
- İç içe map kullanarak; ÃrneÄin
cache.set(min)aslında(max, result)'ı tutmaktadır. Böyleceresultcache.get(min).get(max)Åeklinde alınabilir. - İki deÄeri teke indirerek. Bizim durumumuzda bu
"min,max"Åeklinde bir karakter dizisiniMapâin anahtarı yapmak olabilir. Ayrıca hashing fonksiyonuâu dekoratöre saÄlayabiliriz. Bu fonksiyon da birçok deÄerden bir deÄer yapabilir.
ÃoÄu uygulama için 3. çözüm yeterlidir. Biz de bu çözüm ile devam edeceÄiz.
İkinci görev ise fonksiyona birden fazla argümanın nasıl gönderileceÄidir. Åu anda saklayıcı fonksiyona function(x) Åeklinde tek argüman gönderilmektedir. Bu da func.call(this,x) Åeklinde uygulanır.
Burada kullanılacak diÄer metod func.applyâdır.
Yazımı:
func.apply(context, args)
Bu funcâı this=context ve args için dizi benzeri bir argüman dizisi ile çalıÅtırır.
ÃrneÄin aÅaÄıdaki iki çaÄrı tamamen aynıdır.
func(1, 2, 3);
func.apply(context, [1, 2, 3])
Her ikisi de funcâı 1,2,3argümanları ile çalıÅtırır. Fakat apply ayrıca this=contextâi ayarlar.
function say(time, phrase) {
alert(`[${time}] ${this.name}: ${phrase}`);
}
let user = { name: "John" };
let messageData = ['10:00', 'Hello']; // time, phrase'e dönüÅür.
// this = user olur , messageData liste olarak (time,phrase) Åeklinde gönderilir.
say.apply(user, messageData); // [10:00] John: Hello (this=user)
call argüman listesi beklerken apply dizi benzeri bir obje ile onları alır.
Yayma operatörü Gerisi parametreleri ve yayma operatörleri konusunda ... yayma operatörünün ne iÅ yaptıÄını iÅlemiÅtik. Dizilerin argüman listesi Åeklinde gönderilebileceÄinden bahsemiÅtik. Ãyleyse call ile bunu kullanırsak neredeyse applyâın iÅlevini görebiliriz.
AÅaÄıdaki iki çaÄrı birbirinin aynısıdır:
let args = [1, 2, 3];
func.call(context, ...args); // dizileri yayma operatörü ile liste Åeklinde gönderir.
func.apply(context, args); // aynısını apply ile yapar.
İÅleme daha yakından bakılacak olursa call ile apply arasında oldukça küçük bir fark vardır.
- Yayma operatörü
...list gibi döngülenebilir argümanlarıcalledilmek üzere iletebilir. applyise sadece dizi-benzeriargsalır.
Ãyleyse bu çaÄrılar birbirinin tamamlayıcısıdır. Döngülenebilir beklediÄimizde call, dizi-benzeri beklediÄimizde ise apply çalıÅır.
EÄer args hem döngülenebilir bende dizi ise teknik olarak ikisini de kullanabiliriz, fakat apply muhtemelen daha hızlı olacaktır. Ãünkü tek bir iÅlemden oluÅur. ÃoÄu JavaScript motoru bir kaç call + spread kullanmaktan daha iyi Åekilde optimizasyon yapar.
Applyâın en çok çaÄrıyı diÄer fonksiyona iletirken iÅe yarar:
let wrapper = function() {
return anotherFunction.apply(this, arguments);
};
Buna çaÄrı iletme denir. Saklayıcı sahip olduÄu her Åeyi iletir: this ile argümanları anotherFunctionâa iletir ve sonucunu döner.
Böyle bir saklayıcı kod çaÄırıldıÄında içerideki orjinal fonksiyon çaÄıran tarafından ayrıÅtırılamaz.
Åimdi bunları daha güçlü cachingDecoratırâda iÅleyelim:
let worker = {
slow(min, max) {
alert(`${min},${max} ile çaÄırıldı`);
return min + max;
}
};
function cachingDecorator(func, hash) {
let cache = new Map();
return function() {
let key = hash(arguments); // (*)
if (cache.has(key)) {
return cache.get(key);
}
let result = func.apply(this, arguments); // (**)
cache.set(key, result);
return result;
};
}
function hash(args) {
return args[0] + ',' + args[1];
}
worker.slow = cachingDecorator(worker.slow, hash);
alert( worker.slow(3, 5) ); // works
alert( "Again " + worker.slow(3, 5) ); // same (cached)
Åimdi saklayıcı(wrapper) birçok argüman ile çalıÅabilir.
İki tane deÄiÅiklik oldu:
(*)satırında birhasile argümanlardan tek bir anahtar meydana getirildi. Bunun için basit âbirleÅtirmeâ fonksiyonu kullanılmıÅtır.(3,5)"3,5"Åekline getirildi. Tabi baÅka hash fonksiyonları için daha karmaÅık bir yapı gerekebilir.(**)satırında isefunc.applyile hem kaynak ( this ) hem de saklayıcı argümanları (ne kadar olduÄu önemli deÄil) orjinal fonksiyona iletilmiÅtir.
Metod Ãdünç Alma
Hash fonksiyonunda biraz deÄiÅiklik yapalım:
function hash(args) {
return args[0] + ',' + args[1];
}
Bundan sonra bu fonksiyon sadece iki argümanla çalıÅacak. Bunun yerine belirsiz sayıdaki argümanla çalıÅsa daha iyi olur.
Bunu kullanmanın doÄal yolu arr.join metodudur:
function hash(args) {
return args.join();
}
⦠Malesef bu çalıÅmaz. Ãünkü hash(argümanlar) çaÄırılmakta ve arguments objei hem döngülenebilir hemde dizi-benzeri olduÄundan, fakat gerçek dizi olmadıÄından çalıÅmaz.
Ãyleyse join bu durumda çaÄırılamaz, örneÄin:
function hash() {
alert( arguments.join() ); // Error: arguments.join fonksiyon deÄil.
}
hash(1, 2);
Fakat yine de dizi birleÅtirme kullanmanın kolay bir yolu vardır.
function hash() {
alert( [].join.call(arguments) ); // 1,2
}
hash(1, 2);
Bu cambazlıÄa metod ödünç alma denir.
Normal diziden [].join join ödünç alınır. Sonrasında arguments contexi ile çaÄrı yapılır [].join.call
Peki neden çalıÅır?
Ãünkü gerçek dizinin join metodu oldukça basittir.
Tanımından âolduÄuâ gibi alındıÄında iÅlem Åu Åekildedir:
yapistirilk argüman olsun, eÄer argüman yoksa","ilk argüman olsun.sonucboÅ karakter dizisi olsunthis[0]'ı sonuca ekle.yapistirvethis[1]'i ekleyapistirvethis[2]'i ekle- â¦
this.lengthâe kadarki tüm elemanlar yapıÅtırılana kadar ekle. sonucdön- Return
result.
Teknik olarak thisâi alır ve this[0], this[1] â¦vs. Åeklinde birleÅtirir. Bu Åekilde yazılarak tüm dizi-gibiâlerin this Åeklinde çalıÅmasını saÄlar. Bundan dolayı this=arguments Åeklinde de çalıÅır.
Ãzet
Decoratör fonksiyonun davranıÅını deÄiÅtiren saklayıcılardır(wrapper). İŠhala ana fonksiyonda yapılır.
Genelde gerçek fonksiyonu dekoratör ile deÄiÅtirmek güvenlidir, bir olay haricinde. EÄer orjinal fonksiyon func.calledCount gibi bir özelliÄe sahipse, dekoratör bunu saÄlamayacaktır çünkü bu bir saklayıcıdır. Bundan dolayı kullanırken dikkatli olunmalıdır. Bazı dekoratörler kendine ait özellikler tutarlar.
cachingDecorator kullanmak için bazı metodlar denedik:
- func.call(context, arg1, arg2â¦) â
funcâı verilen kaynak ve argümanlar ile çaÄırır. - func.apply(context, args) â
kaynakâıthisolarak vearray-benzeriargümanları liste olarak iletir.
çaÄrı iletme genelde apply ile gerçekleÅtirilir:
let wrapper = function() {
return original.apply(this, arguments);
}
Ayrıca metod ödünç almaâyı da gördük. Bir metotdan obje alındıÄında ve bu diÄer objenin kaynaÄında(context) çaÄırıldıÄında gerçekleÅir. Dizi metodlarını alıp argümanlara uygulama çokça kullanılır. Bunun alternatifi geriye kalan parametre objelerini kullanmaktır. Bunlar gerçek dizilerdir.
AraÅtırırsanız birçok dekoratör görebilirsiniz. Bu bölümdeki görevleri çözerekte kendinizi geliÅtirebilirsiniz.
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)