← Kursa Dön
📄 Text · 30 min

Proxy ve Reflect

Neden Bu Konu Önemli?

Bir binanın güvenlik görevlisini düşün. Binaya girmek isteyen herkes önce güvenlikten geçer. Güvenlik görevlisi gelen kişiyi kontrol eder: kimliği var mı, randevusu var mı, yasak listesinde mi? Her şey tamamsa içeri alır, yoksa reddeder. Hatta giren kişiyi loglar, bildirim gönderir. Binadaki insanlar bundan habersizdir — güvenlik görevlisi şeffaf bir katman olarak çalışır.

JavaScript'teki Proxy tam olarak bu güvenlik görevlisidir. Bir nesnenin önüne geçer ve o nesneye yapılan tüm işlemleri (okuma, yazma, silme, fonksiyon çağrısı vb.) yakalayıp kontrol edebilir. Reflect ise bu işlemlerin orijinal davranışını çalıştırmak için kullanılan yardımcı API'dir.

Bu kavramlar "metaprogramlama" dünyasına kapı açar: kod yazmak yerine, kodun nasıl davrandığını kontrol eden kod yazarsın. Vue.js'in reactivity sistemi, veri doğrulama (validation), loglama, performans ölçümü gibi güçlü pattern'ler Proxy ile mümkün hale gelir.


Proxy Temelleri

Proxy iki parametre alır:

  1. target: Proxy'lenen orijinal nesne

  2. handler: Yakalanacak işlemleri (trap) tanımlayan nesne

// En basit proxy — hiçbir şey yapmaz, her şeyi geçirir
const hedef = { ad: "Ahmet", yas: 28 };
const handler = {}; // Boş handler — tüm işlemler orijinale yönlendirilir

const proxy = new Proxy(hedef, handler);

console.log(proxy.ad);  // "Ahmet" — direkt hedeften okuyor
proxy.yas = 29;          // Direkt hedefe yazıyor
console.log(hedef.yas); // 29 — hedef güncellendi

Boş handler ile proxy, orijinal nesneyle aynı davranır. Güç, handler'a trap fonksiyonları eklediğimizde ortaya çıkar.


Handler Traps (Tuzaklar)

Handler nesnesine eklenen metotlara "trap" denir. Her trap, belirli bir operasyonu yakalar.

get Trap — Okuma Yakalama

const kullanici = {
  ad: "Ahmet",
  soyad: "Yılmaz",
  yas: 28,
};

const handler = {
  // get trap: nesne özelliği okunduğunda çalışır
  get(target, property, receiver) {
    console.log(`📖 "${property}" okundu`);
    
    if (property in target) {
      return target[property];
    }
    
    return `"${property}" özelliği bulunamadı`;
  },
};

const proxy = new Proxy(kullanici, handler);

console.log(proxy.ad);       // 📖 "ad" okundu → "Ahmet"
console.log(proxy.email);    // 📖 "email" okundu → "email" özelliği bulunamadı

set Trap — Yazma Yakalama

// Veri doğrulama (validation) proxy'si
const validator = {
  set(target, property, value) {
    if (property === "yas") {
      if (typeof value !== "number") {
        throw new TypeError("Yaş bir sayı olmalıdır");
      }
      if (value < 0 || value > 150) {
        throw new RangeError("Yaş 0-150 arasında olmalıdır");
      }
    }
    
    if (property === "email") {
      if (typeof value !== "string" || !value.includes("@")) {
        throw new TypeError("Geçerli bir email adresi giriniz");
      }
    }
    
    target[property] = value;
    return true; // set trap'te true dönmek ZORUNLU (strict mode'da)
  },
};

const kullanici = new Proxy({}, validator);

kullanici.ad = "Ahmet";       // ✅ Sorunsuz
kullanici.yas = 28;            // ✅ Sorunsuz
kullanici.email = "a@b.com";   // ✅ Sorunsuz

// kullanici.yas = -5;         // ❌ RangeError!
// kullanici.yas = "yirmi";    // ❌ TypeError!
// kullanici.email = "invalid"; // ❌ TypeError!

has Trap — in Operatörü Yakalama

// Belirli özellikleri gizleme
const gizliVeriler = {
  ad: "Ahmet",
  _sifre: "gizli123",
  _token: "abc-xyz",
  email: "ahmet@test.com",
};

const handler = {
  has(target, property) {
    // _ ile başlayan özellikleri gizle
    if (property.startsWith("_")) {
      return false;
    }
    return property in target;
  },
};

const proxy = new Proxy(gizliVeriler, handler);

console.log("ad" in proxy);     // true
console.log("email" in proxy);  // true
console.log("_sifre" in proxy); // false — gizli!
console.log("_token" in proxy); // false — gizli!

deleteProperty Trap — Silme Yakalama

// Belirli özelliklerin silinmesini engelleme
const korunanNesne = {
  id: 1,
  ad: "Ahmet",
  rol: "admin",
};

const handler = {
  deleteProperty(target, property) {
    if (property === "id" || property === "rol") {
      throw new Error(`"${property}" özelliği silinemez!`);
    }
    delete target[property];
    return true;
  },
};

const proxy = new Proxy(korunanNesne, handler);

delete proxy.ad;  // ✅ Sorunsuz — ad silindi
// delete proxy.id; // ❌ Error: "id" özelliği silinemez!

apply Trap — Fonksiyon Çağrısı Yakalama

// Fonksiyon çağrılarını loglama ve ölçme
function agirHesaplama(n) {
  let toplam = 0;
  for (let i = 0; i < n; i++) {
    toplam += Math.sqrt(i);
  }
  return toplam;
}

const performansProxy = new Proxy(agirHesaplama, {
  apply(target, thisArg, argumentsList) {
    const basla = performance.now();
    const sonuc = target.apply(thisArg, argumentsList);
    const sure = performance.now() - basla;
    
    console.log(`⏱️ ${target.name}(${argumentsList.join(", ")}) → ${sure.toFixed(2)}ms`);
    return sonuc;
  },
});

performansProxy(1_000_000);
// ⏱️ agirHesaplama(1000000) → 12.45ms

construct Trap — new Operatörü Yakalama

// Singleton pattern — her zaman aynı instance
class Database {
  constructor(url) {
    this.url = url;
    this.connected = false;
    console.log(`Database oluşturuldu: ${url}`);
  }
  
  connect() {
    this.connected = true;
  }
}

let instance = null;

const SingletonDatabase = new Proxy(Database, {
  construct(target, args) {
    if (!instance) {
      instance = new target(...args);
    }
    return instance; // Her zaman aynı instance
  },
});

const db1 = new SingletonDatabase("localhost:5432");
const db2 = new SingletonDatabase("localhost:3306");

console.log(db1 === db2); // true — aynı instance!
console.log(db2.url);     // "localhost:5432" — ilk oluşturulan

Reflect API

Reflect nesnesi, Proxy trap'lerinin varsayılan davranışlarını çalıştırmak için standart metotlar sunar. Her trap'in karşılığı bir Reflect metodu vardır.

Neden Reflect Kullanmalı?

// Reflect OLMADAN — doğrudan nesne operasyonları
const handler = {
  get(target, property) {
    console.log(`Okunuyor: ${property}`);
    return target[property]; // Direkt erişim — sorunlu olabilir
  },
  set(target, property, value) {
    console.log(`Yazılıyor: ${property} = ${value}`);
    target[property] = value;
    return true;
  },
};

// Reflect İLE — tutarlı ve güvenli
const betterHandler = {
  get(target, property, receiver) {
    console.log(`Okunuyor: ${property}`);
    return Reflect.get(target, property, receiver); // Doğru receiver
  },
  set(target, property, value, receiver) {
    console.log(`Yazılıyor: ${property} = ${value}`);
    return Reflect.set(target, property, value, receiver);
  },
};

Reflect kullanmanın avantajları:

  1. receiver parametresini doğru yönetir (prototipal kalıtımda önemli)

  2. Hata fırlatmak yerine false döner

  3. Proxy trap'leriyle birebir eşleşir (aynı parametre sırası)

  4. Operatörleri fonksiyon olarak çağırmayı sağlar

// Reflect metotları — operatörlerin fonksiyon karşılıkları
const nesne = { ad: "Ahmet" };

// Bu ikisi eşdeğer:
console.log("ad" in nesne);          // true
console.log(Reflect.has(nesne, "ad")); // true

// Bu ikisi eşdeğer:
delete nesne.ad;
Reflect.deleteProperty(nesne, "ad");

// Bu ikisi eşdeğer:
Object.keys(nesne);
Reflect.ownKeys(nesne); // Symbol'leri de dahil eder!

Pratik Proxy Kalıpları

1. Değişiklik İzleme (Change Tracking)

// Nesne değişikliklerini otomatik izle
function izlenebilirYap(nesne, onChange) {
  return new Proxy(nesne, {
    set(target, property, value, receiver) {
      const eskiDeger = target[property];
      const sonuc = Reflect.set(target, property, value, receiver);
      
      if (eskiDeger !== value) {
        onChange({
          property,
          eskiDeger,
          yeniDeger: value,
          zaman: new Date().toISOString(),
        });
      }
      
      return sonuc;
    },
    
    deleteProperty(target, property) {
      const eskiDeger = target[property];
      const sonuc = Reflect.deleteProperty(target, property);
      
      onChange({
        property,
        eskiDeger,
        yeniDeger: undefined,
        silindi: true,
        zaman: new Date().toISOString(),
      });
      
      return sonuc;
    },
  });
}

// Kullanım
const kullanici = izlenebilirYap(
  { ad: "Ahmet", yas: 28 },
  (degisiklik) => {
    console.log("📝 Değişiklik:", degisiklik);
  }
);

kullanici.ad = "Mehmet";
// 📝 Değişiklik: { property: "ad", eskiDeger: "Ahmet", yeniDeger: "Mehmet", ... }

kullanici.yas = 29;
// 📝 Değişiklik: { property: "yas", eskiDeger: 28, yeniDeger: 29, ... }

2. Negatif İndeks Desteği (Python Tarzı)

// Python gibi negatif indeks: arr[-1] → son eleman
function negatifIndeks(dizi) {
  return new Proxy(dizi, {
    get(target, property, receiver) {
      const index = Number(property);
      
      // Sayısal ve negatif ise → sondan eriş
      if (!isNaN(index) && index < 0) {
        return target[target.length + index];
      }
      
      return Reflect.get(target, property, receiver);
    },
  });
}

const arr = negatifIndeks([10, 20, 30, 40, 50]);

console.log(arr[0]);   // 10 — normal erişim
console.log(arr[-1]);  // 50 — son eleman!
console.log(arr[-2]);  // 40 — sondan ikinci!
console.log(arr.length); // 5 — normal özellikler çalışır

3. Varsayılan Değerli Nesne

// Olmayan özelliklere varsayılan değer döndür
function varsayilanNesne(nesne, varsayilan) {
  return new Proxy(nesne, {
    get(target, property, receiver) {
      if (property in target) {
        return Reflect.get(target, property, receiver);
      }
      
      // Fonksiyon ise çalıştır, değilse direkt dön
      return typeof varsayilan === "function"
        ? varsayilan(property)
        : varsayilan;
    },
  });
}

// Sözlük — olmayan kelimeler için varsayılan mesaj
const sozluk = varsayilanNesne(
  { hello: "merhaba", world: "dünya" },
  (kelime) => `"${kelime}" çevirisi bulunamadı`
);

console.log(sozluk.hello);   // "merhaba"
console.log(sozluk.goodbye); // "goodbye" çevirisi bulunamadı

// Sayaç — olmayan anahtarlar için 0
const sayac = varsayilanNesne({}, 0);
sayac.tiklamalar = (sayac.tiklamalar || 0) + 1;
console.log(sayac.tiklamalar); // 1
console.log(sayac.ziyaretler); // 0

4. Salt Okunur (Read-Only) Nesne

// Nesneyi tamamen salt okunur yap — derin (deep) freeze
function saltOkunur(nesne) {
  return new Proxy(nesne, {
    set(target, property) {
      throw new Error(
        `Bu nesne salt okunurdur! "${property}" değiştirilemez.`
      );
    },
    deleteProperty(target, property) {
      throw new Error(
        `Bu nesne salt okunurdur! "${property}" silinemez.`
      );
    },
    get(target, property, receiver) {
      const deger = Reflect.get(target, property, receiver);
      
      // İç nesneleri de salt okunur yap (derin proxy)
      if (typeof deger === "object" && deger !== null) {
        return saltOkunur(deger);
      }
      
      return deger;
    },
  });
}

const config = saltOkunur({
  veritabani: {
    host: "localhost",
    port: 5432,
  },
  api: {
    key: "secret",
  },
});

console.log(config.veritabani.host); // "localhost" — okuma serbest
// config.veritabani.port = 3306;    // ❌ Error: salt okunur!
// config.yeniAlan = "test";         // ❌ Error: salt okunur!

Vue.js Reactivity Arkası

Vue.js 3'ün reactivity sistemi Proxy üzerine kuruludur. Basitleştirilmiş bir versiyonunu inceleyelim:

// Vue.js reactivity'nin basitleştirilmiş hali
let aktifEfekt = null;

function reactive(nesne) {
  // Her özellik için bağımlılıkları (dependency) tutan Map
  const bagimliliklar = new Map();
  
  function bagimlilikGetir(property) {
    if (!bagimliliklar.has(property)) {
      bagimliliklar.set(property, new Set());
    }
    return bagimliliklar.get(property);
  }
  
  return new Proxy(nesne, {
    get(target, property, receiver) {
      const deger = Reflect.get(target, property, receiver);
      
      // Eğer aktif bir efekt varsa, bu özelliği bağımlılık olarak kaydet
      if (aktifEfekt) {
        bagimlilikGetir(property).add(aktifEfekt);
      }
      
      return deger;
    },
    
    set(target, property, value, receiver) {
      const sonuc = Reflect.set(target, property, value, receiver);
      
      // Bu özelliğe bağımlı tüm efektleri tekrar çalıştır
      const deps = bagimlilikGetir(property);
      deps.forEach((efekt) => efekt());
      
      return sonuc;
    },
  });
}

function effect(fn) {
  aktifEfekt = fn;
  fn(); // İlk çalıştırma — bağımlılıkları toplar
  aktifEfekt = null;
}

// Kullanım — Vue-like reactivity
const durum = reactive({
  isim: "Ahmet",
  sayac: 0,
});

// Effect: durum değiştiğinde otomatik çalışır
effect(() => {
  console.log(`Merhaba, ${durum.isim}! Sayaç: ${durum.sayac}`);
});
// İlk çalıştırma: "Merhaba, Ahmet! Sayaç: 0"

durum.sayac = 1;
// OTOMATİK: "Merhaba, Ahmet! Sayaç: 1"

durum.isim = "Mehmet";
// OTOMATİK: "Merhaba, Mehmet! Sayaç: 1"

durum.sayac = 2;
// OTOMATİK: "Merhaba, Mehmet! Sayaç: 2"

Bu örnek Vue.js 3'ün ref() ve reactive() fonksiyonlarının temelini gösterir. Gerçek implementasyon çok daha sofistike ama temel prensip aynı: Proxy ile okuma ve yazma işlemlerini yakalayıp, bağımlılık takibi (dependency tracking) ve otomatik güncelleme (trigger) sağlamak.

💡 İpucu: Vue 2 Object.defineProperty() kullanıyordu — bu yüzden yeni eklenen özellikleri otomatik algılayamıyordu (Vue.set() gerekiyordu). Vue 3 Proxy'e geçerek bu sınırlamayı kaldırdı.


Yaygın Hatalar ve Tuzaklar

Hata 1: set Trap'te true Dönmemek

// ❌ Strict mode'da hata — set trap true dönmeli
const proxy = new Proxy({}, {
  set(target, property, value) {
    target[property] = value;
    // return true; ← UNUTULMUŞ!
  },
});

// proxy.x = 1; // TypeError: 'set' on proxy: trap returned falsish
//               // (strict mode'da — ESM dosyaları her zaman strict)

// ✅ Doğrusu
const proxy2 = new Proxy({}, {
  set(target, property, value) {
    target[property] = value;
    return true; // ZORUNLU
  },
});

Hata 2: Proxy Üzerinde typeof ve === Tuzakları

// Proxy şeffaftır ama some checks fail
const hedef = {};
const proxy = new Proxy(hedef, {});

console.log(proxy === hedef);    // false! Farklı referanslar
console.log(typeof proxy);       // "object" — bu doğru

// WeakMap, WeakSet gibi yapılarda proxy key olarak kullanılabilir
// ama hedef ile proxy farklı key'lerdir
const wm = new WeakMap();
wm.set(hedef, "orijinal");
wm.set(proxy, "proxy");

console.log(wm.get(hedef)); // "orijinal"
console.log(wm.get(proxy)); // "proxy" — farklı!

Hata 3: Bazı Built-in Nesneler Proxy ile Uyumsuz

// ⚠️ Map, Set, Date gibi built-in'ler internal slot'lar kullanır
// Proxy bu slot'lara erişemez
const tarih = new Date();
const proxyTarih = new Proxy(tarih, {});

// proxyTarih.getTime(); // TypeError: this is not a Date object

// ✅ Çözüm: get trap'te metotları bind et
const proxyTarih2 = new Proxy(tarih, {
  get(target, property, receiver) {
    const value = Reflect.get(target, property);
    if (typeof value === "function") {
      return value.bind(target); // Target'a bind et
    }
    return value;
  },
});

console.log(proxyTarih2.getTime()); // ✅ Çalışır

Tüm Proxy Trap'leri Tablosu

// Toplam 13 trap mevcut:
const tumTraplar = {
  // Özellik okuma
  get(target, property, receiver) {},
  
  // Özellik yazma
  set(target, property, value, receiver) {},
  
  // 'in' operatörü
  has(target, property) {},
  
  // delete operatörü
  deleteProperty(target, property) {},
  
  // Object.keys, for...in
  ownKeys(target) {},
  
  // Object.getOwnPropertyDescriptor
  getOwnPropertyDescriptor(target, property) {},
  
  // Object.defineProperty
  defineProperty(target, property, descriptor) {},
  
  // Object.getPrototypeOf
  getPrototypeOf(target) {},
  
  // Object.setPrototypeOf
  setPrototypeOf(target, proto) {},
  
  // Object.isExtensible
  isExtensible(target) {},
  
  // Object.preventExtensions
  preventExtensions(target) {},
  
  // Fonksiyon çağrısı — fn()
  apply(target, thisArg, argumentsList) {},
  
  // new operatörü — new Fn()
  construct(target, argumentsList, newTarget) {},
};

Özet

Bu derste JavaScript'in metaprogramlama araçlarını öğrendik:

  • Proxy bir nesnenin önüne geçen şeffaf bir katmandır. Okuma, yazma, silme gibi temel operasyonları yakalayıp (trap) özel davranış ekler.

  • Reflect API, Proxy trap'lerinin varsayılan davranışını çalıştırmak için tutarlı metotlar sunar. target[property] yerine Reflect.get(target, property, receiver) kullanmak daha güvenlidir.

  • Veri doğrulama (validation), değişiklik izleme (change tracking), salt okunur nesneler, negatif indeks gibi güçlü pattern'ler Proxy ile zarif şekilde uygulanabilir.

  • Vue.js 3'ün reactivity sistemi Proxy'nin en ünlü kullanım örneğidir: get trap'te bağımlılık toplama, set trap'te otomatik güncelleme tetikleme.

  • Bazı built-in nesneler (Date, Map, Set) internal slot'lar kullandığı için Proxy ile çalışırken bind gibi özel işlem gerektirebilir.

Bu bölümle Modern JavaScript (ES6+) konularını tamamladık! Bir sonraki bölümde OOP ve Prototipler dünyasına gireceğiz.