JavaScript’in Takipçi Nesnesi ‘Proxy’
JavaScript’in Takipçi Nesnesi ‘Proxy’
Bir nesneye her atama(set) veya okuma(get) yapıldığında belirli işlemleri nasıl yapacağımızı hiç düşündünüz mü? OOP kullanılabilen bir dille daha önce uğraştıysanız bu soruya haklı olarak “Tabii ki getter ve setter kullanarak.” cevabını vermiş olabilirsinizdir, JavaScript de bu özelliğe sahiptir ancak asıl nesnemize getter/setter eklemek istemediğimizde veya daha fazla işlemi takip etmek istediğimizde ne kullanacağız peki? İşte burada da Proxy nesneleri yardımımıza koşuyor.
Proxy Nedir?
ECMAScript 6 ile hayatımıza giren Proxy nesnesi, bir objeyi baz alarak get, set, yeni property tanımlama gibi temel obje operasyonlarını takip edebileceğimiz yeni bir obje oluşturur. Bu sayede obje üzerindeki işlemlere göre aksiyona geçebilir, veri doğrulayabilir, serileştirebilir, log’layabilir yahut benzeri işlemleri yapabiliriz.
Proxy üzerinden hedef nesnenin özellikleri değiştirildikçe hem proxy’deki veriler hem de hedef nesnenin içindekilerin değişeceği kullanılırken göz önünde bulundurulmalıdır.
Nasıl tanımlanır?
Proxy constructure’ının iki ana parametresi vardır. Bunlardan birincisi kopyasını çıkaracağımız hedef nesne ikincisi ise işlemler tetiklendiğinde çalışacak mantıksal yapımızın olduğu bir handler’dır. Her handler’ın kendine ait parametreleri vardır.
// target yerine orijinal nesnemizi,
// handler yerine takip fonksiyonlarımızın bulunduğu nesneyi yazarız.
// new Proxy(target, handler);
let user = {
name: 'Alihan',
surname: 'SARAC',
age: 21,
};
const handler = {
// ilgili method'lar
};
let userProxy = new Proxy(user, handler);
Hangi obje işlemlerini takip edebiliriz?
Objelerin temel işlemlerinin birçoğunu takip
edebiliriz. Bunların bir kısmı proxy üzerinden tetiklenebilirken bir kısmını da Object
sınıfının method’ları ile tetikleyebiliriz.
get
: proxy’den okuma yapıldığında tetiklenirset
:proxy’e atama yapılırken tetiklenirhas
:in
operatörü kullanıldığında tetiklenir.apply
: fonksiyondan üretilen proxy’lerde fonksiyon çağrıldığında tetiklenir.ownKeys
:Object.getOwnPropertyNames
veObject.getOwnPropertySymbols
ile proxy kullanıldığında tetiklenirconstruct
:new
operatörü kullanıldığında tetiklenirisExtensible
:Object.isExtensible
ile proxy kullanıldığında tetiklenir.deleteProperty
:delete
operatörü proxy üzerinde kullanılınca tetiklenir.defineProperty
:Object.defineProperty
ile proxy kullanıldığında tetiklenir.setPrototypeOf
:Object.setPrototypeOf
ile proxy kullanıldığında tetiklenir.getOwnPropertyDescriptor
:Object.getOwnPropertyDescriptor
ile proxy çağrıldığında tetiklenir.preventExtensions
:Object.preventExtensions
ile proxy kullanıldığında tetiklenir.
Nasıl kullanılır?
Proxy handler method’larının içine tanımlandığı nesnenin güncel halini parametre olarak gönderir. Key(property)’lere özel handler tanımlanamadığı için eğer key’lerle ilgili bir işlem yapılıyorsa key’in ismini de parametre geçer ve ardından ilgili işleme özel parametreleri de gönderir.
En temel 3 şey olan atama, getirme ve silme işlemlerine gelin birlikte bakalım.
Get:
// target: hedef nesnemizin kendisi
// property: okuma işlemi yapılan key
// reciever: proxy'nin kendisi yada ondan kalıtım alan bir nesne.
//
// temel işlemlerde "reciever" kullanmamıza gerek yoktur
// sadece ilk iki parametre alınabilir
get( target, property, reciever){
// mantıksal işlemler
return "geri dönülecek değer";
}
Get fonksiyonunun içinden target[property]
diyerek şu an okuma yapılan değeri alabilir,
ilgili işlemleri yaptıktan sonra istediğimiz değeri return
diyerek kullanıcıya dönebiliriz.
Set:
// target: hedef nesnemizin kendisi
// property: okuma işlemi yapılan key
// value: property'e atanmak istenen değerdir
// reciever: proxy'nin kendisi yada ondan kalıtım alan bir nesne.
//
// temel işlemlerde "reciever" kullanmamıza gerek yoktur
// sadece ilk iki parametre alınabilir
get( target, property, value, reciever){
// mantıksal işlemler
return true;
}
Set fonksiyonu mutlaka geriye atama
yapıldığına dair bool bir değer dönmelidir. Fonksiyonunun içinden target[property]
diyerek atama yapmalısınız.
Delete:
// target: hedef nesnemizin kendisi
// property: okuma işlemi yapılan key
deleteProperty( target, property){
// mantıksal işlemler
delete target[property];
return true;
}
deleteProperty
, delete
operatörü kullanıldığında tetiklenir ve silinme
yapıldığının anlaşılması için bool bir değer döndürmelidir.
Geri alınabilir Proxy’ler
Eğer Proxy oluştururken Proxy.revocable
fonksiyonunu kullanırsak proxy özelliği
kapatılabilir bir proxy oluşturmuş oluruz. Oluşturulma şekli ve kullanımı normal proxy ile temel olarak
aynıdır, tek fark mu fonksiyonun geriye proxy değil onu verecek ve iptal edecek olan nesneyi vermesidir.
letrevocable
=Proxy.revocable(target, handler);
let userProxy = revocable.proxy;
revocable
oluşturulduktan sonra revocable.revoke
method’u ile proxy nesnesinin proxy’si
kapatılır.
Unutulmamalıdır ki proxy kapatıldıktan sonra yukarıdaki “Hangi
obje işlemlerini takip edebiliriz?” alanında bahsedilen tüm operasyonlar TypeError
fırlatacaktır.
Örnek Kullanım Senaryoları:
Value Validation:
Proxy’lerin en popüler kullanım alanlarından biri de veri doğrulamasıdır. Atama işlemlerinde minimum-maksimum değer, email ve yazım tipi doğrulaması gibi birçok işlemde kullanılabilir.
Senaryomuzda bir kullanıcı olduğunu varsayalım. Yaş değerinin 0'dan büyük olması gerektiğini, silme işleminde sadece kullanıcının silme tarihi geçtiyse silinebileceğini ve sadece test kullanıcılarının şifresinin okunabileceğini varsayalım.
Orijinal nesnemizi tanımlayalım.
let user = {
name: 'alihan',
age: 21,
password: '123456as.',
isTestUser: true,
expireAt: new Date(100000000000),
}
Proxy’nin handler’ını tanımlayalım.
const userProxyHandler = {
get(target, key){
let value = target[key];
if(key === 'password' && !isTestUser){
value = '***';
// gelen okuma password için yapılıyorsa ve kullancı test kullanıcısı değilse şifre '***' şeklinde gösterilir.
}
return value;
}
set(target, key, value){
if(key === 'age' && value < 0){
throw new Error('yaş değeri 0'a eşit veya büyük olmalıdır');
}
target[key] = value;
return true;
}
deleteProperty(target, key){
if(Date.now() > target[expireAt]){
throw new Error('Kullanıcı bu tarihte silinemez');
}
delete target[key];
}
}
Değişen key’leri ve eski değerlerini nesne içinde tutma:
Veritabanlarıyla uğraşıyorsanız, hele de benim gibi bir ORM yazmayı deniyorsanız her bir işlemin maliyeti sizin için oldukça önemlidir. İçinde sadece bir alanı değişmiş büyük bir nesneyi sisteme tekrardan kaydetmek gibi bir masraf kesinlikle istemeyeceğimiz bir şeydir bu durumu çözmek için hangi alanlarda değişiklik yapıldığını tutabilir ve sadece o alanı kaydedebiliriz. Proxy‘ler tam da bu iş için yaratılmış. Hadi nasıl yapacağımıza bakalım.
İlk önce handler’a bir set olayı yazmalıyız.
Burada objemizin içinde _degisenDegerler
adında, obje
tipinde bir key tutacağız ve değişiklikleri buradan takip edebileceğiz. Her set işlemi çalıştığında ilgili
key
ilk önce target içindeki _degisenDegerler
‘e eklenmeli ve şu anki verisi buraya
yazılmalı ardından ise target
‘ın içindeki key
alanı gelen value
‘yu tutacak şekilde güncellenmeli böylece eski veri
_degisenDegerler
içine yazılmış ve yerine yenisi atanmış
olur. Veritabanına kayıt yapıldıktan sonra da _degisenDegerler
içindeki tüm key’ler kaldırılabilir.
let model = {
isim: 'Alihan',
_degisenDegerler: {},
};
let modelProxy = new Proxy(model, {
set(target, key, deger){
target._degisenDegerler[key] = target[key];
target[key] = deger;
return true;
}
});
modelProxy.isim = 'ayşe';
modelProxy.yas=21;
console.log({ modelProxy });
//{
// "modelProxy": {
// "isim": "ayşe",
// "yas": 21,
// "_degisenDegerler": {
// isim: "Alihan"
// yas: undefined
// },
// }
//}