Ders 05 / 17
Değişmezlik Teknikleri
Dondurma, mühürleme ve genişletmeyi engelleme işlemlerinin kapsamı, sığ kalmalarının sonuçları, derin dondurma ve değişiklik yerine yeni nesne üretme kalıpları.
İçindekiler
Önceki ders tek bir özelliği writable: false ile kilitledi. Bir nesnenin bütününü
korumak için her alana ayrı tanım yazmak zahmetlidir ve yeni alan eklenmesini de
engellemez. Dil, nesne düzeyinde çalışan üç işlem sunar.
Bu dersin konusu değiştirilemezlik (immutability): bir nesnenin yaratıldıktan sonra içeriğinin değişmemesi. Terim, önceki derste geçen sınıf değişmezi (invariant) ile karıştırılmamalıdır; değişmez bir kuraldır, değiştirilemezlik ise bir yetenektir. Programlama Temelleri kursundaki Fonksiyonel Programlamaya Giriş dersi bu yeteneğin gerekçesini kurmuştu: değeri değişmeyen bir nesne, kaç yerden başvurulursa başvurulsun sürprizsizdir.
Dondurma
Object.freeze bir nesnenin tüm kendi özelliklerini yazılamaz ve yapılandırılamaz yapar,
üstelik nesneye yeni özellik eklenmesini de yasaklar.
const kayit = Object.freeze({ sensor: "S-01", deger: 21.4, zaman: 1000, }); console.log(Object.isFrozen(kayit)); console.log(Object.isExtensible(kayit)); const denemeler = [ ["var olan alani yazma", () => { kayit.deger = 30; }], ["yeni alan ekleme", () => { kayit.birim = "santigrat"; }], ["alan silme", () => { delete kayit.zaman; }], ["tanimi degistirme", () => { Object.defineProperty(kayit, "deger", { value: 30 }); }], ["prototip degistirme", () => { Object.setPrototypeOf(kayit, null); }], ]; for (const [ad, islem] of denemeler) { try { islem(); console.log(`${ad}: gecti`); } catch (hata) { console.log(`${ad}: ${hata.constructor.name}`); } } console.log(JSON.stringify(kayit)); const tanim = Object.getOwnPropertyDescriptor(kayit, "deger"); console.log(`yazilabilir=${tanim.writable}, yapilandirilabilir=${tanim.configurable}`);
true
false
var olan alani yazma: TypeError
yeni alan ekleme: TypeError
alan silme: TypeError
tanimi degistirme: TypeError
prototip degistirme: TypeError
{"sensor":"S-01","deger":21.4,"zaman":1000}
yazilabilir=false, yapilandirilabilir=false
Beş değiştirme yolu da kapanmıştır; prototip bağı bile sabitlenir. Bu ders bir modül dosyasında, yani katı kipte çalıştığı için denemeler hata yükseltir. Katı olmayan kipte aynı işlemler sessizce başarısız olur — önceki dersteki ayrımın aynısı.
Dondurma geri alınamaz. Object.unfreeze gibi bir işlem yoktur, çünkü
yapılandırılabilirlik kapandıktan sonra tanımlayıcı bir daha değiştirilemez. Bir nesneyi
dondurmak, o nesne üzerinde verilmiş nihai bir karardır.
Dondurma Sığdır
Object.freeze yalnızca nesnenin kendi özelliklerine dokunur. İçerideki nesneler ve
prototipteki tanımlar kapsam dışında kalır.
const olcumDavranisi = { bicimle() { return `${this.sensor}: ${this.deger}`; }, }; const kayit = Object.create(olcumDavranisi); kayit.sensor = "S-01"; kayit.deger = 21.4; kayit.kaynak = { dosya: "olcum-2.csv", satir: 17 }; Object.freeze(kayit); console.log(Object.isFrozen(kayit)); console.log(Object.isFrozen(kayit.kaynak)); kayit.kaynak.satir = 99; console.log(kayit.kaynak.satir); olcumDavranisi.bicimle = function () { return `[degistirildi] ${this.sensor}`; }; console.log(kayit.bicimle()); console.log(Object.isFrozen(Object.getPrototypeOf(kayit)));
true false 99 [degistirildi] S-01 false
İki sızıntı vardır. Birincisi tanıdıktır: kaydın kaynak alanı bir başvurudur; donan şey
başvurunun kendisidir, gösterdiği nesne değil. Programlama Temelleri kursundaki Değer ve
Referans ile Aktarım dersindeki sığ kopya tartışmasının aynısıdır.
İkincisi prototiple ilgilidir ve bu kursa özgüdür. Kaydın prototip bağı dondurulmuştur ama prototip nesnesi dondurulmamıştır. Zincirdeki bir davranışın değiştirilmesi, dondurulmuş nesnenin davranışını değiştirir. Nesnenin verisi kadar davranışının da sabit kalması isteniyorsa prototip nesnesi de ayrıca dondurulmalıdır.
Üç İşlemin Kapsamı
Dondurma en katı seçenektir; iki gevşek biçimi vardır. Object.preventExtensions
yalnızca yeni özellik eklenmesini yasaklar, Object.seal buna silmeyi de ekler. Farkı
görmek için üçünü aynı denemelerden geçirmek yeterlidir.
function denetle(ad, nesne) { const sonuclar = []; try { nesne.deger = 30; sonuclar.push(`yazma=${nesne.deger === 30 ? "oldu" : "olmadi"}`); } catch { sonuclar.push("yazma=hata"); } try { nesne.birim = "santigrat"; sonuclar.push(`ekleme=${Object.hasOwn(nesne, "birim") ? "oldu" : "olmadi"}`); } catch { sonuclar.push("ekleme=hata"); } try { delete nesne.sensor; sonuclar.push(`silme=${Object.hasOwn(nesne, "sensor") ? "olmadi" : "oldu"}`); } catch { sonuclar.push("silme=hata"); } console.log(`${ad}: ${sonuclar.join(", ")}`); } denetle("acik ", { sensor: "S-01", deger: 21.4 }); denetle("genisletme", Object.preventExtensions({ sensor: "S-01", deger: 21.4 })); denetle("muhurlu ", Object.seal({ sensor: "S-01", deger: 21.4 })); denetle("donmus ", Object.freeze({ sensor: "S-01", deger: 21.4 }));
acik : yazma=oldu, ekleme=oldu, silme=oldu genisletme: yazma=oldu, ekleme=hata, silme=oldu muhurlu : yazma=oldu, ekleme=hata, silme=hata donmus : yazma=hata, ekleme=hata, silme=hata
Kapsamlar iç içedir: genişletmeyi engelleme ⊂ mühürleme ⊂ dondurma. Mühürleme, alan kümesi sabit ama değerleri değişebilen nesneler için uygundur — bir yapılandırma kaydının biçimini kilitleyip içeriğini serbest bırakmak gibi. Dondurma ise nesneyi tümüyle sabit kılar.
Derin Dondurma
Sığlığı aşmak için özyinelemeli bir gezinti gerekir. Programlama Temelleri kursundaki Özyineleme dersinin kalıbı doğrudan uygulanır: taban durumu nesne olmayan değerlerdir, indirgeme adımı her alanın kendisini dondurmaktır. Çevrimsel başvuruların sonsuz özyinelemeye yol açmaması için ziyaret edilenler işaretlenir.
function derinDondur(nesne, gorulen = new WeakSet()) { if (nesne === null || typeof nesne !== "object" || gorulen.has(nesne)) { return nesne; } gorulen.add(nesne); for (const ad of Object.getOwnPropertyNames(nesne)) { const tanim = Object.getOwnPropertyDescriptor(nesne, ad); if ("value" in tanim) derinDondur(tanim.value, gorulen); } return Object.freeze(nesne); } const kayit = derinDondur({ sensor: "S-01", deger: 21.4, kaynak: { dosya: "olcum-2.csv", satir: 17 }, etiketler: ["ham", "sicaklik"], }); console.log(Object.isFrozen(kayit)); console.log(Object.isFrozen(kayit.kaynak)); console.log(Object.isFrozen(kayit.etiketler)); try { kayit.kaynak.satir = 99; } catch (hata) { console.log(`ic nesne yazma: ${hata.constructor.name}`); } try { kayit.etiketler.push("yeni"); } catch (hata) { console.log(`diziye ekleme: ${hata.constructor.name}`); } console.log(JSON.stringify(kayit));
true
true
true
ic nesne yazma: TypeError
diziye ekleme: TypeError
{"sensor":"S-01","deger":21.4,"kaynak":{"dosya":"olcum-2.csv","satir":17},"etiketler":["ham","sicaklik"]}
Diziler de nesne olduğu için aynı işlemden geçer; dondurulmuş bir diziye ekleme yapmak
hata yükseltir. getOwnPropertyNames kullanılması sayılamaz alanların da kapsanmasını
sağlar; erişimci özelliklerde value alanı bulunmadığından okuyucu fonksiyon
çalıştırılmaz. WeakSet seçimi bilinçlidir: işaretleme geçicidir ve gezilen nesnelerin
ömrünü uzatmamalıdır. Bu koleksiyonun ayrıntısı İleri Koleksiyonlar konusundadır.
Derin dondurmanın maliyeti nesne sayısıyla doğru orantılıdır; büyük ağaçlarda her yaratımda uygulanması pahalıdır. Yaygın uygulama, dondurmayı yalnızca paylaşılan ve uzun ömürlü verilerde kullanmaktır.
Değiştirmek Yerine Üretmek
Nesne dondurulduğunda “alanı güncelle” isteği bir soruya dönüşür: güncellenmiş yeni nesne nasıl üretilir? Nesne yayma bunun için yeterli görünür, ama zincirle ilgili bir bedeli vardır.
class OlcumKaydi { constructor(sensor, deger, zaman) { this.sensor = sensor; this.deger = deger; this.zaman = zaman; Object.freeze(this); } bicimle() { return `${this.sensor}@${this.zaman}: ${this.deger}`; } degerleDegistir(yeniDeger) { const kopya = Object.create(Object.getPrototypeOf(this)); Object.assign(kopya, this, { deger: yeniDeger }); return Object.freeze(kopya); } } const ilk = new OlcumKaydi("S-01", 21.4, 1000); const yayilarak = { ...ilk, deger: 30 }; const yontemle = ilk.degerleDegistir(30); console.log(ilk.bicimle()); console.log(JSON.stringify(yayilarak)); console.log(typeof yayilarak.bicimle); console.log(Object.getPrototypeOf(yayilarak) === OlcumKaydi.prototype); console.log(Object.isFrozen(yayilarak)); console.log(yontemle.bicimle()); console.log(Object.getPrototypeOf(yontemle) === OlcumKaydi.prototype); console.log(yontemle instanceof OlcumKaydi); console.log(Object.isFrozen(yontemle)); console.log(ilk.deger);
S-01@1000: 21.4
{"sensor":"S-01","deger":30,"zaman":1000}
undefined
false
false
S-01@1000: 30
true
true
true
21.4
Yayma ile üretilen nesne doğru veriyi taşır ama zincirini kaybetmiştir: prototipi
Object.prototype olur, bicimle bulunamaz, instanceof sonucu olumsuzdur ve nesne
donmuş değildir. Kursun ilk dersindeki cümle burada karşılığını bulur — kopyalama kendi
özellikleri taşır, zinciri taşımaz.
degerleDegistir yöntemi zinciri Object.create(Object.getPrototypeOf(this)) ile
kurduğu için sonuç gerçek bir ölçüm kaydıdır. Yapıcı yerine mevcut prototipin
kullanılması ayrıca bir kazanç sağlar: alt sınıflardan çağrıldığında alt sınıfın
prototipi korunur.
Son satır kalıbın amacını doğrular: ilk kayıt değişmemiştir. Değişiklik istendiğinde eski nesne yerinde kalır, yenisi üretilir. Bu, Programlama Temelleri kursundaki Fonksiyonel Programlamaya Giriş dersinde tanıtılan değiştirilemezlik yaklaşımının nesne modelindeki karşılığıdır.
Özet
Object.freezekendi özellikleri yazılamaz ve yapılandırılamaz yapar, yeni özellik eklemeyi ve prototip değiştirmeyi yasaklar; geri alınamaz.- Dondurma sığdır: iç nesneler ve prototip nesnesi etkilenmez, bu yüzden davranış hâlâ değiştirilebilir.
- Kapsamlar iç içedir — genişletmeyi engelleme, mühürleme, dondurma; ortadaki seçenek alan kümesini sabitler, değerleri serbest bırakır.
- Derin dondurma özyinelemeli bir gezintidir; çevrimlere karşı ziyaret işaretlemesi gerekir ve maliyeti nesne sayısıyla artar.
- Dondurulmuş nesnede güncelleme, yeni nesne üretmeye dönüşür; nesne yayma zinciri kaybettiği için prototip açıkça kurulmalıdır.
Sonraki Adım
Bu konu boyunca yöntemler hep this üzerinden çalıştı: bicimle sensörü kayıttan,
erişimci ham değeri nesnenin kendisinden okudu. Ancak this değerinin ne olacağı
fonksiyonun nerede tanımlandığına değil, nasıl çağrıldığına bağlıdır — zincirden gelen
bir fonksiyon, hiç tanımadığı bir nesne üzerinde de çalışabilir. Sonraki konu bu bağlanma
kurallarını dört başlık altında ayırt edecek ve yöntemin nesnesinden koptuğu durumları
inceleyecek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.