Nesne davranışını değiştirmek için JavaScript Proxy desenlerini keşfedin. Doğrulama, sanallaştırma, izleme ve diğer ileri teknikleri kod örnekleriyle öğrenin.
JavaScript Proxy Desenleri: Nesne Davranışı Değişiminde Uzmanlaşma
JavaScript Proxy nesnesi, nesneler üzerindeki temel işlemleri yakalamak ve özelleştirmek için güçlü bir mekanizma sunar. Bu yetenek, nesne davranışını kontrol etmek için çok çeşitli tasarım desenlerine ve ileri tekniklere kapı açar. Bu kapsamlı rehber, çeşitli Proxy desenlerini keşfeder ve bunların kullanımlarını pratik kod örnekleriyle gösterir.
JavaScript Proxy Nedir?
Bir Proxy nesnesi, başka bir nesneyi (hedef) sarmalar ve onun işlemlerini yakalar. Tuzak (trap) olarak bilinen bu işlemler, özellik arama, atama, numaralandırma ve fonksiyon çağırma gibi işlemleri içerir. Proxy, bu işlemlerden önce, sonra veya bu işlemlerin yerine yürütülecek özel mantık tanımlamanıza olanak tanır. Proxy'nin temel kavramı, JavaScript dilinin davranışını manipüle etmenizi sağlayan "metaprogramlama"yı içerir.
Bir Proxy oluşturmak için temel sözdizimi şöyledir:
const proxy = new Proxy(target, handler);
- target: Proxy'lemek istediğiniz orijinal nesne.
- handler: Hedef üzerindeki işlemleri Proxy'nin nasıl yakalayacağını tanımlayan metotları (tuzakları) içeren bir nesne.
Yaygın Proxy Tuzakları (Traps)
Handler nesnesi birkaç tuzak tanımlayabilir. İşte en sık kullanılanlardan bazıları:
- get(target, property, receiver): Özellik erişimini yakalar (örneğin,
obj.property
). - set(target, property, value, receiver): Özellik atamasını yakalar (örneğin,
obj.property = value
). - has(target, property):
in
operatörünü yakalar (örneğin,'property' in obj
). - deleteProperty(target, property):
delete
operatörünü yakalar (örneğin,delete obj.property
). - apply(target, thisArg, argumentsList): Fonksiyon çağrılarını yakalar (hedef bir fonksiyon olduğunda).
- construct(target, argumentsList, newTarget):
new
operatörünü yakalar (hedef bir kurucu fonksiyon olduğunda). - getPrototypeOf(target):
Object.getPrototypeOf()
çağrılarını yakalar. - setPrototypeOf(target, prototype):
Object.setPrototypeOf()
çağrılarını yakalar. - isExtensible(target):
Object.isExtensible()
çağrılarını yakalar. - preventExtensions(target):
Object.preventExtensions()
çağrılarını yakalar. - getOwnPropertyDescriptor(target, property):
Object.getOwnPropertyDescriptor()
çağrılarını yakalar. - defineProperty(target, property, descriptor):
Object.defineProperty()
çağrılarını yakalar. - ownKeys(target):
Object.getOwnPropertyNames()
veObject.getOwnPropertySymbols()
çağrılarını yakalar.
Proxy Desenleri ve Kullanım Alanları
Şimdi bazı yaygın Proxy desenlerini ve bunların gerçek dünya senaryolarında nasıl uygulanabileceğini inceleyelim:
1. Doğrulama (Validation)
Doğrulama deseni, özellik atamalarına kısıtlamalar uygulamak için bir Proxy kullanır. Bu, veri bütünlüğünü sağlamak için kullanışlıdır.
const validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('Yaş bir tam sayı değil');
}
if (value < 0) {
throw new RangeError('Yaş negatif olmayan bir tam sayı olmalıdır');
}
}
// Değeri saklamak için varsayılan davranış
obj[prop] = value;
// Başarıyı belirtir
return true;
}
};
let person = {};
let proxy = new Proxy(person, validator);
proxy.age = 25; // Geçerli
console.log(proxy.age); // Çıktı: 25
try {
proxy.age = 'young'; // TypeError fırlatır
} catch (e) {
console.log(e); // Çıktı: TypeError: Yaş bir tam sayı değil
}
try {
proxy.age = -10; // RangeError fırlatır
} catch (e) {
console.log(e); // Çıktı: RangeError: Yaş negatif olmayan bir tam sayı olmalıdır
}
Örnek: Kullanıcı verilerinin doğrulanması gereken bir e-ticaret platformunu düşünün. Bir proxy, yaş, e-posta formatı, şifre gücü ve diğer alanlar üzerinde kurallar uygulayarak geçersiz verilerin saklanmasını önleyebilir.
2. Sanallaştırma (Tembel Yükleme - Lazy Loading)
Tembel yükleme olarak da bilinen sanallaştırma, maliyetli kaynakların yüklenmesini gerçekten ihtiyaç duyulana kadar erteler. Bir Proxy, gerçek nesne için bir yer tutucu görevi görebilir ve onu yalnızca bir özelliğe erişildiğinde yükler.
const expensiveData = {
load: function() {
console.log('Maliyetli veri yükleniyor...');
// Zaman alan bir işlemi simüle et (örneğin, veritabanından veri çekme)
return new Promise(resolve => {
setTimeout(() => {
resolve({
data: 'Bu maliyetli veridir'
});
}, 2000);
});
}
};
const lazyLoadHandler = {
get: function(target, prop) {
if (prop === 'data') {
console.log('Veriye erişiliyor, gerekirse yükleniyor...');
return target.load().then(result => {
target.data = result.data; // Yüklenen veriyi sakla
return result.data;
});
} else {
return target[prop];
}
}
};
const lazyData = new Proxy(expensiveData, lazyLoadHandler);
console.log('İlk erişim...');
lazyData.data.then(data => {
console.log('Veri:', data); // Çıktı: Veri: Bu maliyetli veridir
});
console.log('Sonraki erişim...');
lazyData.data.then(data => {
console.log('Veri:', data); // Çıktı: Veri: Bu maliyetli veridir (önbellekten yüklendi)
});
Örnek: Çok sayıda ayrıntı ve ilgili medya içeren kullanıcı profillerine sahip büyük bir sosyal medya platformu hayal edin. Tüm profil verilerini hemen yüklemek verimsiz olabilir. Bir Proxy ile sanallaştırma, önce temel profil bilgilerinin yüklenmesine ve ardından yalnızca kullanıcı bu bölümlere gittiğinde ek ayrıntıların veya medya içeriğinin yüklenmesine olanak tanır.
3. Günlükleme ve İzleme (Logging and Tracking)
Proxy'ler, özellik erişimini ve değişikliklerini izlemek için kullanılabilir. Bu, hata ayıklama, denetim ve performans izleme için değerlidir.
const logHandler = {
get: function(target, prop, receiver) {
console.log(`GET ${prop}`);
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value) {
console.log(`SET ${prop} to ${value}`);
target[prop] = value;
return true;
}
};
let obj = { name: 'Alice' };
let proxy = new Proxy(obj, logHandler);
console.log(proxy.name); // Çıktı: GET name, Alice
proxy.age = 30; // Çıktı: SET age to 30
Örnek: Ortaklaşa doküman düzenleme uygulamasında, bir Proxy, doküman içeriğinde yapılan her değişikliği izleyebilir. Bu, bir denetim izi oluşturulmasına, geri al/yinele işlevselliğinin etkinleştirilmesine ve kullanıcı katkıları hakkında bilgi sağlanmasına olanak tanır.
4. Salt Okunur Görünümler (Read-Only Views)
Proxy'ler, nesnelerin salt okunur görünümlerini oluşturarak yanlışlıkla yapılan değişiklikleri önleyebilir. Bu, hassas verileri korumak için kullanışlıdır.
const readOnlyHandler = {
set: function(target, prop, value) {
console.error(`${prop} özelliği ayarlanamaz: nesne salt okunur`);
return false; // Ayarlama işleminin başarısız olduğunu belirtir
},
deleteProperty: function(target, prop) {
console.error(`${prop} özelliği silinemez: nesne salt okunur`);
return false; // Silme işleminin başarısız olduğunu belirtir
}
};
let data = { name: 'Bob', age: 40 };
let readOnlyData = new Proxy(data, readOnlyHandler);
try {
readOnlyData.age = 41; // Bir hata fırlatır
} catch (e) {
console.log(e); // 'set' tuzağı false döndürdüğü için hata fırlatılmaz.
}
try {
delete readOnlyData.name; // Bir hata fırlatır
} catch (e) {
console.log(e); // 'deleteProperty' tuzağı false döndürdüğü için hata fırlatılmaz.
}
console.log(data.age); // Çıktı: 40 (değişmedi)
Örnek: Bazı kullanıcıların hesap bilgilerine salt okunur erişimi olduğu bir finansal sistemi düşünün. Bu kullanıcıların hesap bakiyelerini veya diğer kritik verileri değiştirmesini önlemek için bir Proxy kullanılabilir.
5. Varsayılan Değerler (Default Values)
Bir Proxy, eksik özellikler için varsayılan değerler sağlayabilir. Bu, kodu basitleştirir ve null/undefined kontrollerini önler.
const defaultValuesHandler = {
get: function(target, prop, receiver) {
if (!(prop in target)) {
console.log(`${prop} özelliği bulunamadı, varsayılan değer döndürülüyor.`);
return 'Varsayılan Değer'; // Veya başka uygun bir varsayılan
}
return Reflect.get(target, prop, receiver);
}
};
let config = { apiUrl: 'https://api.example.com' };
let configWithDefaults = new Proxy(config, defaultValuesHandler);
console.log(configWithDefaults.apiUrl); // Çıktı: https://api.example.com
console.log(configWithDefaults.timeout); // Çıktı: timeout özelliği bulunamadı, varsayılan değer döndürülüyor. Varsayılan Değer
Örnek: Bir yapılandırma yönetim sisteminde, bir Proxy eksik ayarlar için varsayılan değerler sağlayabilir. Örneğin, bir yapılandırma dosyası bir veritabanı bağlantı zaman aşımı belirtmiyorsa, Proxy önceden tanımlanmış bir varsayılan değeri döndürebilir.
6. Meta Veri ve Ek Açıklamalar (Metadata and Annotations)
Proxy'ler, orijinal nesneyi değiştirmeden ek bilgi sağlayan meta verileri veya ek açıklamaları nesnelere ekleyebilir.
const metadataHandler = {
get: function(target, prop, receiver) {
if (prop === '__metadata__') {
return { description: 'Bu, nesne için meta veridir' };
}
return Reflect.get(target, prop, receiver);
}
};
let article = { title: 'Proxy\'lere Giriş', content: '...' };
let articleWithMetadata = new Proxy(article, metadataHandler);
console.log(articleWithMetadata.title); // Çıktı: Proxy'lere Giriş
console.log(articleWithMetadata.__metadata__.description); // Çıktı: Bu, nesne için meta veridir
Örnek: Bir içerik yönetim sisteminde, bir Proxy, makalelere yazar bilgisi, yayın tarihi ve anahtar kelimeler gibi meta veriler ekleyebilir. Bu meta veriler, içeriği aramak, filtrelemek ve kategorize etmek için kullanılabilir.
7. Fonksiyon Yakalama (Function Interception)
Proxy'ler fonksiyon çağrılarını yakalayarak günlükleme, doğrulama veya diğer ön veya son işleme mantığını eklemenize olanak tanır.
const functionInterceptor = {
apply: function(target, thisArg, argumentsList) {
console.log('Fonksiyon şu argümanlarla çağrılıyor:', argumentsList);
const result = target.apply(thisArg, argumentsList);
console.log('Fonksiyonun döndürdüğü değer:', result);
return result;
}
};
function add(a, b) {
return a + b;
}
let proxiedAdd = new Proxy(add, functionInterceptor);
let sum = proxiedAdd(5, 3); // Çıktı: Fonksiyon şu argümanlarla çağrılıyor: [5, 3], Fonksiyonun döndürdüğü değer: 8
console.log(sum); // Çıktı: 8
Örnek: Bir bankacılık uygulamasında, bir Proxy, işlem fonksiyonlarına yapılan çağrıları yakalayabilir, her işlemi günlüğe kaydedebilir ve işlemi gerçekleştirmeden önce dolandırıcılık tespit kontrolleri yapabilir.
8. Kurucu Yakalama (Constructor Interception)
Proxy'ler kurucu (constructor) çağrılarını yakalayarak nesne oluşturmayı özelleştirmenize olanak tanır.
const constructorInterceptor = {
construct: function(target, argumentsList, newTarget) {
console.log(target.name, 'sınıfının yeni bir örneği şu argümanlarla oluşturuluyor:', argumentsList);
const obj = new target(...argumentsList);
console.log('Yeni örnek oluşturuldu:', obj);
return obj;
}
};
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let ProxiedPerson = new Proxy(Person, constructorInterceptor);
let person = new ProxiedPerson('Alice', 28); // Çıktı: Person sınıfının yeni bir örneği şu argümanlarla oluşturuluyor: ['Alice', 28], Yeni örnek oluşturuldu: Person { name: 'Alice', age: 28 }
console.log(person);
Örnek: Bir oyun geliştirme çatısında, bir Proxy oyun nesnelerinin oluşturulmasını yakalayabilir, otomatik olarak benzersiz kimlikler atayabilir, varsayılan bileşenler ekleyebilir ve bunları oyun motoruna kaydedebilir.
İleri Düzey Hususlar
- Performans: Proxy'ler esneklik sunsa da, bir performans yükü getirebilirler. Özellikle performansa duyarlı uygulamalarda, Proxy kullanmanın faydalarının performans maliyetlerinden daha ağır bastığından emin olmak için kodunuzu karşılaştırmalı olarak test etmek ve profilini çıkarmak önemlidir.
- Uyumluluk: Proxy'ler JavaScript'e nispeten yeni bir eklentidir, bu nedenle eski tarayıcılar onları desteklemeyebilir. Eski ortamlarla uyumluluğu sağlamak için özellik tespiti veya polyfill'ler kullanın.
- İptal Edilebilir Proxy'ler (Revocable Proxies):
Proxy.revocable()
yöntemi, iptal edilebilen bir Proxy oluşturur. Bir Proxy'yi iptal etmek, daha fazla işlemin yakalanmasını önler. Bu, güvenlik veya kaynak yönetimi amaçları için yararlı olabilir. - Reflect API: Reflect API, Proxy tuzaklarının varsayılan davranışını gerçekleştirmek için yöntemler sağlar.
Reflect
kullanmak, Proxy kodunuzun dil belirtimiyle tutarlı bir şekilde davranmasını sağlar.
Sonuç
JavaScript Proxy'leri, nesne davranışını özelleştirmek için güçlü ve çok yönlü bir mekanizma sağlar. Çeşitli Proxy desenlerinde uzmanlaşarak daha sağlam, sürdürülebilir ve verimli kod yazabilirsiniz. İster doğrulama, sanallaştırma, izleme veya diğer ileri teknikleri uyguluyor olun, Proxy'ler nesnelerin nasıl erişildiğini ve manipüle edildiğini kontrol etmek için esnek bir çözüm sunar. Her zaman performans etkilerini göz önünde bulundurun ve hedef ortamlarınızla uyumluluğu sağlayın. Proxy'ler, güçlü metaprogramlama tekniklerini mümkün kılan modern JavaScript geliştiricisinin cephaneliğindeki önemli bir araçtır.
Daha Fazla Keşif
- Mozilla Geliştirici Ağı (MDN): JavaScript Proxy
- JavaScript Proxy'lerini Keşfetmek: Smashing Magazine Makalesi