Proxy, bir nesnenin önüne geçen ve o nesne üzerindeki temel işlemleri (özellik okuma, yazma, silme, in kontrolü, fonksiyon çağırma gibi) yakalayıp yeniden tanımlamanızı sağlayan bir yapıdır. new Proxy(target, handler) ile oluşturulur; handler içindeki get, set gibi fonksiyonlara "trap" denir. Reflect ise bu trap'lerle aynı adlı metotlar sunan bir isim alanıdır ve trap içinden varsayılan davranışa dönmenin en temiz yoludur.
Proxy en çok doğrulama, loglama, varsayılan değer verme ve değişiklik izleme için kullanılır. Bu yazıda trap listesini, çalışan doğrulama ve loglama örneklerini, Proxy.revocable kullanımını ve özel alanlar, iç slotlar ile invariant kuralları gibi önemli kısıtları görüyorsunuz.
Temel kullanım: get trap'i
Handler boşsa proxy hedef nesne gibi davranır ve tüm işlemleri ona iletir. Bir trap tanımladığınızda o işlem sizin fonksiyonunuzdan geçer. Aşağıdaki proxy, olmayan özellikler için undefined yerine 0 döndürür:
const sayac = new Proxy({}, {
get(hedef, ozellik) {
return ozellik in hedef ? hedef[ozellik] : 0;
},
});
sayac.elma = 3;
console.log(sayac.elma, sayac.armut); // 3 0Trap'ler ve karşılık gelen işlemler
![Trap'ler ve karşılık gelen işlemler: obj.x, obj["x"], obj.x = 1, "x" in obj, deleteProperty](/assets/blog/javascript-proxy-reflect-b2.webp)
Her nesnenin [[Get]], [[Set]] gibi iç metotları vardır ve dildeki her etkileşim sonunda bunlardan birine iner. Proxy bu iç metotların her birine bir trap sunar. Reflect üzerinde de aynı adlı statik metotlar bulunur.
| Trap | Yakaladığı işlem | Reflect karşılığı |
|---|---|---|
| get | obj.x, obj["x"] | Reflect.get |
| set | obj.x = 1 | Reflect.set |
| has | "x" in obj | Reflect.has |
| deleteProperty | delete obj.x | Reflect.deleteProperty |
| ownKeys | Object.keys, Reflect.ownKeys | Reflect.ownKeys |
| defineProperty | Object.defineProperty | Reflect.defineProperty |
| getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor | Reflect.getOwnPropertyDescriptor |
| getPrototypeOf / setPrototypeOf | Object.getPrototypeOf / setPrototypeOf | Reflect.getPrototypeOf / setPrototypeOf |
| isExtensible / preventExtensions | Object.isExtensible / preventExtensions | Reflect.isExtensible / preventExtensions |
| apply | fonksiyon çağrısı f() | Reflect.apply |
| construct | new F() | Reflect.construct |
apply ve construct yalnızca hedef bir fonksiyon olduğunda anlamlıdır.
Doğrulama örneği: set trap'i
set trap'i dört argüman alır: hedef, özellik adı, yeni değer ve alıcı (receiver). Atama başarılıysa true döndürmelidir. Kuralı geçen değerleri Reflect.set ile hedefe yazmak, varsayılan davranışı (setter'lar dahil) korur.
const kural = {
set(hedef, ozellik, deger, alici) {
if (ozellik === "yas") {
if (!Number.isInteger(deger)) throw new TypeError("yaş tam sayı olmalı");
if (deger < 0 || deger > 150) throw new RangeError("yaş 0 ile 150 arasında olmalı");
}
return Reflect.set(hedef, ozellik, deger, alici);
},
};
const kisi = new Proxy({}, kural);
kisi.yas = 30;
console.log(kisi.yas); // 30
kisi.yas = "otuz"; // TypeError: yaş tam sayı olmalı
kisi.yas = 200; // RangeError: yaş 0 ile 150 arasında olmalıLoglama örneği: Reflect ile iletme

Aşağıdaki izle fonksiyonu okuma, yazma ve silme işlemlerini loglar, sonra işi Reflect ile hedefe bırakır. Dizi örneği ilginç bir ayrıntıyı gösterir: push gibi metotlar da iç metotlarla çalıştığı için tek bir push çağrısı birden fazla trap tetikler.
function izle(nesne, ad) {
return new Proxy(nesne, {
get(hedef, ozellik, alici) {
console.log(`[okuma] ${ad}.${String(ozellik)}`);
return Reflect.get(hedef, ozellik, alici);
},
set(hedef, ozellik, deger, alici) {
console.log(`[yazma] ${ad}.${String(ozellik)} = ${JSON.stringify(deger)}`);
return Reflect.set(hedef, ozellik, deger, alici);
},
deleteProperty(hedef, ozellik) {
console.log(`[silme] ${ad}.${String(ozellik)}`);
return Reflect.deleteProperty(hedef, ozellik);
},
});
}
const ayar = izle({ tema: "açık" }, "ayar");
ayar.tema = "koyu"; // [yazma] ayar.tema = "koyu"
console.log(ayar.tema); // [okuma] ayar.tema, sonra: koyu
delete ayar.tema; // [silme] ayar.tema
const liste = izle([], "liste");
liste.push("a");
// [okuma] liste.push
// [okuma] liste.length
// [yazma] liste.0 = "a"
// [yazma] liste.length = 1String(ozellik) kullanmamızın nedeni, özellik adının bir symbol olabilmesidir; symbol'ler şablon metne doğrudan eklenemez. Bkz. Symbol nedir.
has, ownKeys ve apply trap'leri
has trap'i in operatörünü, ownKeys ise anahtar listeleyen işlemleri yakalar. Aşağıdaki örnek alt çizgiyle başlayan alanları bu iki işlemden gizler; ancak doğrudan okumayı engellemez. Gerçek bir gizleme için get trap'ini de yazmanız gerekir.
const gizli = new Proxy({ ad: "Ali", _sifre: "1234" }, {
has(hedef, ozellik) {
if (String(ozellik).startsWith("_")) return false;
return Reflect.has(hedef, ozellik);
},
ownKeys(hedef) {
return Reflect.ownKeys(hedef).filter((k) => typeof k !== "string" || !k.startsWith("_"));
},
});
console.log("_sifre" in gizli, "ad" in gizli); // false true
console.log(Object.keys(gizli)); // [ 'ad' ]
console.log(gizli._sifre); // 1234 (get trap'i yok, okuma engellenmedi)function topla(a, b) { return a + b; }
const olculu = new Proxy(topla, {
apply(hedef, thisArg, argumanlar) {
console.log("çağrı:", argumanlar);
return Reflect.apply(hedef, thisArg, argumanlar);
},
});
console.log(olculu(2, 3));
// çağrı: [ 2, 3 ]
// 5Proxy.revocable: erişimi geri almak
Proxy.revocable(target, handler), proxy ve revoke alanlarına sahip bir nesne döndürür. revoke() çağrıldıktan sonra proxy üzerindeki her işlem TypeError fırlatır; typeof ise hiçbir trap tetiklemediği için çalışmaya devam eder. MDN, bunun hedef nesnenin ömrünü kontrol etmenin bir yolu olduğunu belirtir: proxy iptal edildiğinde hedef ve handler bağlantısı koparılır.
const { proxy, revoke } = Proxy.revocable({ deger: 1 }, {});
console.log(proxy.deger); // 1
revoke();
proxy.deger;
// TypeError: Cannot perform 'get' on a proxy that has been revoked
console.log(typeof proxy); // objectKısıtlar ve tuzaklar

Proxy, hedefle aynı nesne değildir
new Proxy(hedef, {}) === hedef ifadesi false döner. Kimlik karşılaştırması yapan kod (örneğin bir Set veya WeakMap anahtarı) proxy ile orijinali farklı görür.
Özel alanlar ve iç slotlar iletilmez
Boş handler'lı bir proxy düz nesnelerde sorunsuz çalışır, ama #özel alanları olan sınıf örneklerinde ve Map gibi iç slot kullanan yerleşik nesnelerde çalışmaz. Getter çalışırken this proxy'nin kendisi olduğu için özel alana erişilemez. Çözüm, değeri doğrudan hedeften okumaktır.
class Kasa {
#bakiye = 100;
get bakiye() { return this.#bakiye; }
}
new Proxy(new Kasa(), {}).bakiye;
// TypeError: Cannot read private member #bakiye from an object whose class did not declare it
console.log(new Proxy(new Kasa(), { get(h, o) { return h[o]; } }).bakiye); // 100
new Proxy(new Map(), {}).size;
// TypeError: Method get Map.prototype.size called on incompatible receiver #<Map>Metotlar için de aynı sorun vardır; MDN, dönen fonksiyonun this değerini hedefe yönlendiren bir sarmalayıcı örneği verir.
Invariant kuralları
Proxy her şeyi değiştiremez. Hedefte yazılamaz ve yapılandırılamaz (non-writable, non-configurable) bir özellik varsa get trap'i onun gerçek değerini döndürmek zorundadır. Bu kuralları çiğneyen trap'ler TypeError fırlatır:
const sabit = {};
Object.defineProperty(sabit, "pi", { value: 3.14, writable: false, configurable: false });
new Proxy(sabit, { get() { return 3; } }).pi;
// TypeError: 'get' on proxy: property 'pi' is a read-only and non-configurable data property
// on the proxy target but the proxy did not return its actual value (expected '3.14' but got '3')set trap'i false döndürürse
set trap'i false döndürdüğünde strict mode'daki atama TypeError fırlatır, sloppy mode'da ise sessizce yok sayılır. Reflect.set ise hata yerine false döndürür. Modüller otomatik strict olduğu için bu fark özellikle önemlidir; ayrıntı strict mode yazısında.
// dosya: ornek.mjs (strict)
const salt = new Proxy({}, { set() { return false; } });
salt.x = 1;
// TypeError: 'set' on proxy: trap returned falsish for property 'x'
console.log(Reflect.set(salt, "x", 1)); // falseTrap içinde proxy'yi tekrar kullanmak
Trap içinde receiver (yani proxy) üzerinden aynı işlemi yapmak trap'i yeniden tetikler ve sonsuz döngüye girer. Hedefe Reflect ile ya da doğrudan hedef üzerinden erişin.
const yanlis = new Proxy({}, {
get(hedef, ozellik, alici) { return alici[ozellik]; },
});
yanlis.a;
// RangeError: Maximum call stack size exceededPratik senaryo: salt okunur görünüm
Bir yapılandırma nesnesini uygulamanın geri kalanına verirken değiştirilmesini istemiyorsanız yazma, silme ve özellik tanımlama trap'lerini aynı fonksiyona bağlayabilirsiniz. Object.freeze da benzer bir sonuç verir; fark, proxy'nin her denemede kendi mesajınızla hata fırlatması ve orijinal nesneyi dondurmamasıdır. Orijinal nesneye referansı olan kod onu değiştirmeye devam edebilir, proxy ise yalnızca kendi üzerinden yapılan işlemleri engeller.
function saltOkunur(nesne) {
const engelle = () => { throw new TypeError("bu nesne salt okunur"); };
return new Proxy(nesne, { set: engelle, deleteProperty: engelle, defineProperty: engelle });
}
const ayar = saltOkunur({ port: 3000 });
console.log(ayar.port); // 3000
ayar.port = 80; // TypeError: bu nesne salt okunur
delete ayar.port; // TypeError: bu nesne salt okunur
Object.defineProperty(ayar, "yeni", { value: 1 }); // TypeError: bu nesne salt okunurNesnelerin dondurulması ve özellik tanımlayıcıları hakkında daha fazla bilgi için Object metotları yazısına bakabilirsiniz. Proxy'yi her sorun için ilk araç olarak görmeyin: davranışı gizli ve dolaylı hale getirdiği için kodu okuyan kişinin trap'leri bilmesi gerekir. Basit bir doğrulama için setter ya da açık bir fonksiyon çoğu zaman daha anlaşılırdır.
Reflect neden kullanılır?
Reflect bir kurucu değildir; Math gibi yalnızca statik metotlar içerir. Ana kullanım alanı trap içinde varsayılan davranışa dönmektir. Bunun yanında başarılı olup olmadığını boolean ile bildiren metotlar da sunar: Object.defineProperty başarısızlıkta hata fırlatırken Reflect.defineProperty false döndürür, böylece try...catch yerine if kullanabilirsiniz. Reflect.has, in operatörünün fonksiyon halidir.
Sık Sorulan Sorular
Proxy performansı etkiler mi?
Her işlem bir fonksiyon çağrısından geçtiği için ek iş yapılır. Yerel kaynaklarda bunun için bir ölçüm yoktur; sık çalışan kodda kullanıyorsanız kendi senaryonuzda ölçmeniz gerekir.
Proxy ile nesnedeki tüm değişiklikleri izleyebilir miyim?
Yalnızca proxy üzerinden yapılan işlemleri. Hedef nesneye doğrudan referansı olan kod, proxy'yi atlayarak değişiklik yapabilir. İç içe nesneleri izlemek için get trap'inde dönen nesneleri de proxy ile sarmanız gerekir.
Reflect olmadan trap yazılabilir mi?
Evet, hedef[ozellik] = deger gibi doğrudan işlemler de çalışır. Reflect ise receiver argümanını doğru iletir, getter ve setter'larda this değerini korur ve her iç metot için ayrı sözdizimi hatırlamanızı gerektirmez.
Proxy hangi nesnelerle kullanılabilir?
Hedef herhangi bir nesne olabilir: düz nesne, dizi, fonksiyon, hatta başka bir proxy. İç slot kullanan yerleşik nesnelerde ve özel alanlı sınıflarda yukarıdaki kısıtları hesaba katın.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: JavaScript öğrenme rehberi · Object metotları · Symbol nedir · Class ve özel alanlar · Strict mode