fatiherikli

Underscore.js javascript ile fonksiyonel programlamayı kolaylaştırmak amaçlı geliştirilen bir kütüphanedir. Backbone.js'in de geliştiricisi olan DocumentCloud tarafından yürütülen bir projedir. Hatta underscore.js için backbone dökümantasyonunda -hard dependency- olarak belirtilmekte. Yani backbone.js'yi kullanırken projenize underscore.js'i de dahil etmek zorundasınız.

Underscore.js üzerinde çeşitli liste, nesne ve fonksiyon araçları bulunmakta. Bunların içinden en hoşuma gidenlerini buraya yazacağım.

All & Any

Bu fonksiyonlar python'da da built-in olarak bulunmakta. Bazen gerçekten çok faydalı olabiliyor.

All fonksiyonu verdiğiniz listenin içindeki değerleri bakarak boolean kontrolü yapmakta. Eğer hepsi True ise fonksiyon True olarak dönüş yapar, değil ise False. Any ise listenin içinde en az bir tane True değer varsa True döndürmekte.

_.all([1,true,false], _.identity); // false  
_.all([1, true, true], _.identity); // true   
_.any([1, true, false], _.identity); // true

_.identity sizi yanıltmasın. Verdiğiniz değerin aynısını geri döndüren bir fonksiyondur. Bunun yerine kendi fonksiyonunuzu yazıp gelen parametreye göre boolean bir değer döndürebilirsiniz.

Pluck

Map fonksiyonunun bir kısa yolu diyebiliriz. Diyelim ki içinde objeler olan bir listeniz var ve bu liste içindenki objelerden sadece bir özellik alarak yeni bir liste oluşturmak istiyorsunuz. Bunu ilk önce map, sonra da pluck ile yapalım;

var buddies = [   
    {name: 'fatih', location: 'istanbul'},   
    {name: 'ramazan', location: 'istanbul'},   
    {name: 'yigit', location: 'istanbul'},   
]  
_.map(buddies, function (item) { return item.name });  
>> ["fatih", "ramazan", "yigit"]  
_.pluck(buddies, 'name');   
>> ["fatih", "ramazan", "yigit"]

Gördüğünüz gibi map ile isimleri almak için ayrıdan bir fonksiyon yazdık. Pluck tam olarak bunun kısayolu. Ayrıdan bir fonksiyon yazmadan direk bir obje içinden attribute alarak liste oluşturabilmektesiniz.

Ayrıca bunu python'da operator.attrgetter metoduyla yapabilmekteyiz.

Shuffle

Adından da anlaşılacağı üzere verdiğiniz listeyi randomize eden bir fonksiyondur. Fisher–Yates shuffle algoritmasına göre sıralamaktaymış.

_.shuffle(["fatih", "ramazan", "yigit"]);  
>> ["ramazan", "fatih", "yigit"]

Bunlar liste ya da javascript'teki terimiyle Array fonksiyonlarıydı. Şimdi ise biraz daha fonksiyonel programlama temelli olanlarından bahsedeceğim.

Memoize

Bir optimizasyon pattern'ı olan Memoization'ı kolaylaştıran bir fonksiyondur. Basit bir şekilde açıklayacak olursak; diyelim ki bir hesaplama fonksiyonunuz var. En klasik örneklerinden fibonacci. Memoize edilen bir fonksiyonda verdiğiniz parametreye göre hesaplama bir kere yapılır, belleğe alınır ve bir daha bu fonksiyonu aynı parametre ile çağırdığınızda tekrar hesaplamak yerinde direk bellektekini size döndürür.

var calculate = function (number) {  
    console.log('calculating...');  
    return number * number;  
 }  
var lazy_calculate = _.memoize(calculate);  
lazy_calculate(10);  
>> calculating...  
>> 100  
lazy_calculate(10);  
>> 100  
lazy_calculate(10);  
>> 100  
lazy_calculate(20);  
>> calculating...  
>> 400  
lazy_calculate(20);  
>> 400

Bu fonksiyonun aynısını Django üzerinde django.utils.functional paketinde bulabilirsiniz.

Debounce

Debouncing işlemini muhakkak ki herkes kendi yöntemleriyle yapmıştır. Açıklamak birazcık güç olacak; debounced yaptığınız bir fonksiyon çağırılmadan önce bir süre bekler. Bu süreyi siz belirlersiniz, örneğin 2 saniye. Eğer 2 saniye boyunca tekrar çağırılırsa bu süre sıfırlanır.

Peki neden böyle bir şey yapayım ki diye soracak olursanız şöyle bir örnek verebilirim.

Diyelim ki otomatik tamamlama özelliği olan bir arama kutusu yapıyorsunuz. Kullanıcı her tuşa bastığında bir ajax isteği gönderip uygun sonuçları getiriyorsunuz. Eğer burada ajax isteği yaparken debouncing işlemini yapmazsanız kullanıcı 8 karakterli bir kelime yazdığında sunucuya tam 8 adet ajax isteği göndermek durumunda kalırsınız.

Bu problemi debouncing işlemi ile aşabilirsiniz. Debouncing yaptığınızda kullanıcının her yazdığı harf ile birlikte sunucuya istek yapmak yerine kullanıcının yazacağı kelime bittiğinde 2 saniye gibi bir süre bekleterek istek sayısını 1'e indirebilirsiniz.

Bir örnek yapalım;

var request = function () {  
   console.log("hello, this response from server");   
}  
var debounced_request = _.debounce(request, 2000);  
debounced_request();  
debounced_request();  
debounced_request();  
debounced_request();  
// sonra 4 istek yapmamıza rağmen 2 saniye sonra 1 adet yanıt gelecek.  
>> 'hello, this response from server'

Bu işlem ile ilgili Don't spam your server ve Debouncing javascript methods makalelerini okumanızı tavsiye edebilirim.

Throttle

Bu fonksiyon debouncing işlemine benzemektedir. Throttling işlemini bir fonksiyonun kullanımının zaman ile kısıtlanılması gerektiği yerlerde kullanmaktayız. Debounced edilmiş bir fonksiyon peş peşe çağırıldığında aradaki süre sizin belirlediğiniz süreden kısa ise belirttiğiniz sürenin dolmasını bekler.

Örnek verecek olursak; bir refresh butonunuz var. Kullanıcı buraya tıkladığında sayfada bir bölüm refresh oluyor. Eğer buna kısıtlama getirmezseniz kullanıcı bu butona 30 kere peş peşe bastığında 30 tane istek ard arda yapılır. Bunu throttle ile aşabilirsiniz.

var request = function () {  
   console.log("hello, this response from server");   
}  
var throttled_request = _.debounce(request, 2000);  
throttled_request();  
>> 'hello, this response from server'  
// 1 sn. bekliyoruz  
throttled_request();  
// cevap 1. sn sonra geliyor. çünkü throttle ederken 2 saniye süre verdik.  
>> 'hello, this response from server'

Bind

Bu fonksiyon ECMAScript 5'ten itibaren artık built-in olarak Javascript'te bulunmakta. Ancak cross-browser bir javascript kodu yazmak istiyorsanız Underscore.js üzerinden kullanmanız daha faydalı olabilir.

Diyelim ki  şöyle bir fonksiyonunuz var;

var hello = function () {  
   return "hello, " + this.name;   
}  
hello();  
>> 'hello undefined'  
window.name = 'fatih';  
hello();  
>> 'hello fatih'

Gördüğünüz gibi ilk önce name öğesini istediğimizde undefined cevabını aldık. Global olarak tanımladığımız bir fonksiyonun scope'u window'dur. Window objesinde name tanımı olmadığı için yoktu. Tanımladıktan sonra fonksiyondan beklediğimiz cevabı alabildik.

Karışık bir örnek oldu. Çünkü javascript'teki this keyword'u diğer dillerdeki this keyword'üne hiç benzemiyor.

Bunun gibi durumlarda fonksiyonları call ya da apply ile çağırarak scope'unu değiştirebilmekteyiz. Call ve apply arasındaki fark call'un parametreleri fonksiyon parametresi olarak, apply'ın ise liste şeklinde almasıdır.

Bind fonksiyonu bu işlemi daha da kolaylaştırmakta. Size apply işlemini yapan bir fonksiyon döndürmektedir.

var hello = function () {  
   return "hello, " + this.name;   
}  
hello();  
>> 'hello undefined'  
hello.apply({ name: 'fatih' });  
>> 'hello fatih'  
var binded_hello = _.bind(hello, { name: 'fatih' });  
binded_hello();   
>> 'hello fatih'

Gündelik hayattan başka bir örnek daha vermek istiyorum. Mesela backbone projelerini incelediyseniz bind ve bindAll fonksiyonlarının çok çok kullanıldığını görmüşsünüzdür. Çünkü bu işlemi yapmadan iç içe fonksiyonlar yazdığınızda this ile objeyi alıp başka bir değişkene atamak durumunda kalıyoruz. Örneğin var self = this; gibi.

Gündelik hayattan bir örnek demiştik;

var request = {   
    'result_text' : 'congratulations',  
    'post' : function () {  
       // callback fonksiyonuna scope atamasi yapiyoruz.  
       $.post('/send', _.bind(function (response) {  
           console.log(this.result_text);  
       }, this));  
       // bu sayede callback fonksiyonunda result objesini kullanabileceğiz.  
    }  
}

Burada apply ya da call kullanamazdık, çünkü callback fonksiyonunu biz değil jquery'nin get methodu çağırmakta. Bind kullanmak en mantıklı çözüm.

Ayrıca bu süpersonik çok amaçlı fonksiyon ile currying işlemi de yapabilmekteyiz. Currying bir fonksiyonun parametrelerini önceden vererek başka bir fonksiyon oluşturmaktır.

var people = function (country, name) {  
   return 'hello ' + country + ', hello ' + name;  
}  
people('turkey', 'fatih');  
>> 'hello turkey, hello fatih'  
var turkey_people = _.bind(people, {}, 'turkey');  
turkey_people('fatih');  
>> 'hello turkey, hello fatih'

Sadece bind fonksiyonunu için underscore.js'yi yüklemek istemiyorsanız şu snippet'i kullanabilirsiniz;

[https://github.com/taylanpince/jquery- class/blob/master/src/bind.js](https://github.com/taylanpince/jquery- class/blob/master/src/bind.js)

Ayrıca Python Istanbul buluşmasında console.log'u bind etme konusunda beni beni aydınlattığı için Burak Yiğit Kaya'ya teşekkürlerimi sunarım :)

Bir de  kendisinin twitter üzerinden yazdığı bir öneri üzerine şuna da değinmek istiyorum;

ECMAScript 5 ile Underscore.js üzerinde de göreceğiniz forEach, map, reduce, reduceRight, some, filter, every gibi bazı array fonksiyonları native bir şekilde gelmekte. Underscore.js bunların kontrolünü yapıp eğer browser destekliyorsa native olanı kullanmaktadır.

Önerebileceğim Underscore.js kaynakları

Diğerleri

comments powered by Disqus