İçeriğe geç
academia.sh

Ders 04 / 17

Özellik Tanımlayıcıları

Bir özelliğin değerinin yanında taşıdığı yazılabilirlik, sayılabilirlik ve yapılandırılabilirlik bayrakları; erişimci özellikler ve tanımlayıcıları koruyan kopyalama.

İçindekiler

Önceki ders iki kez aynı yere geldi: sınıf yöntemleri for...in çıktısında görünmüyor, elle yazılan atamalar görünüyordu; ve okunanDeger bir okuma gibi yazıldığı hâlde fonksiyon çalıştırıyordu. İkisi de tek bir gerçeğin sonucudur.

Bir özellik yalnızca “ad → değer” çiftinden ibaret değildir. Her özelliğin yanında, davranışını belirleyen bir özellik tanımlayıcısı (property descriptor) durur. Bu ders tanımlayıcının alanlarını tek tek açar; ardından değer yerine fonksiyon taşıyan erişimci özellikleri ele alır.

Tanımlayıcıyı Okumak

Object.getOwnPropertyDescriptor, bir nesnenin kendi özelliğinin tanımlayıcısını döndürür. Veri özelliklerinde dört alan bulunur: value, writable, enumerable, configurable.

function bayraklar(nesne, ad) {
  const tanim = Object.getOwnPropertyDescriptor(nesne, ad);
  if (tanim === undefined) return `${ad}: tanim yok`;
  if ("value" in tanim) {
    return `${ad}: veri, yazilabilir=${tanim.writable}, sayilabilir=${tanim.enumerable}, yapilandirilabilir=${tanim.configurable}`;
  }
  return `${ad}: erisimci, okuyucu=${typeof tanim.get}, yazici=${typeof tanim.set}, sayilabilir=${tanim.enumerable}`;
}

class OlcumKaydi {
  constructor(sensor, deger) {
    this.sensor = sensor;
    this.deger = deger;
  }
  bicimle() {
    return `${this.sensor}: ${this.deger}`;
  }
}

const kayit = new OlcumKaydi("S-01", 21.4);
const duzKayit = { sensor: "S-02", deger: 19.8 };

Object.defineProperty(duzKayit, "birim", { value: "santigrat" });

console.log(bayraklar(kayit, "sensor"));
console.log(bayraklar(OlcumKaydi.prototype, "bicimle"));
console.log(bayraklar(duzKayit, "sensor"));
console.log(bayraklar(duzKayit, "birim"));
console.log(bayraklar(kayit, "bicimle"));
sensor: veri, yazilabilir=true, sayilabilir=true, yapilandirilabilir=true
bicimle: veri, yazilabilir=true, sayilabilir=false, yapilandirilabilir=true
sensor: veri, yazilabilir=true, sayilabilir=true, yapilandirilabilir=true
birim: veri, yazilabilir=false, sayilabilir=false, yapilandirilabilir=false
bicimle: tanim yok

Üç kural çıkar. Sıradan atamayla ya da nesne değişmeziyle üretilen özelliklerin üç bayrağı da açıktır. Sınıf yöntemleri sayılamaz olarak tanımlanır — önceki dersteki for...in farkının kaynağı budur. Object.defineProperty çağrısında belirtilmeyen her bayrak kapalı sayılır; bu yüzden birim özelliği yazılamaz, sayılamaz ve yapılandırılamaz çıkmıştır. Son satır ise işlevin adındaki “kendi” sözcüğünü hatırlatır: bicimle prototipte durduğu için kayıt üzerinde tanımı yoktur.

Yazılabilirlik

writable bayrağı kapalıysa atama değeri değiştirmez. Değiştirmemenin iki farklı biçimi vardır ve aradaki fark, kodun katı kipte çalışıp çalışmadığına bağlıdır.

const kayit = { sensor: "S-01", deger: 21.4 };

Object.defineProperty(kayit, "birim", {
  value: "santigrat",
  writable: false,
  enumerable: true,
  configurable: false,
});

console.log(kayit.birim);

try {
  kayit.birim = "fahrenhayt";
} catch (hata) {
  console.log(`${hata.constructor.name} yakalandi`);
}
console.log(kayit.birim);

function katiOlmayanYazma(nesne) {
  return new Function("n", "n.birim = 'fahrenhayt'; return n.birim;")(nesne);
}
console.log(katiOlmayanYazma(kayit));

try {
  Object.defineProperty(kayit, "birim", { value: "kelvin" });
} catch (hata) {
  console.log(`${hata.constructor.name} yakalandi`);
}
console.log(kayit.birim);
santigrat
TypeError yakalandi
santigrat
santigrat
TypeError yakalandi
santigrat

Modül dosyaları ve sınıf gövdeleri katı kipte çalışır; orada yazılamayan özelliğe atama hata yükseltir. new Function ile üretilen gövde katı kipte değildir, bu yüzden aynı atama sessizce başarısız olur — dördüncü satırdaki değişmemiş değer bunu gösterir. Sessiz başarısızlık, hatanın atamadan çok sonra fark edilmesine yol açtığı için katı kip tercih edilir.

Son iki satır configurable bayrağının etkisidir. Yapılandırılabilirlik kapatıldığında tanımlayıcı bir daha değiştirilemez ve özellik silinemez; bu, geri alınamayan tek bayraktır. Bu nedenle configurable: false bilinçli bir karar olmalıdır.

Sayılabilirlik

enumerable bayrağı, özelliğin listelemelere girip girmeyeceğini belirler. Etkisi geniş bir işlem kümesine yayılır.

const kayit = { sensor: "S-01", deger: 21.4 };

Object.defineProperty(kayit, "kaynakDosya", {
  value: "olcum-2.csv",
  writable: true,
  enumerable: false,
  configurable: true,
});

console.log(kayit.kaynakDosya);
console.log(Object.keys(kayit).join(","));
console.log(Object.getOwnPropertyNames(kayit).join(","));
console.log(JSON.stringify(kayit));
console.log(JSON.stringify({ ...kayit }));
console.log(JSON.stringify(Object.assign({}, kayit)));
console.log("kaynakDosya" in kayit);
console.log(Object.entries(kayit).length);
olcum-2.csv
sensor,deger
sensor,deger,kaynakDosya
{"sensor":"S-01","deger":21.4}
{"sensor":"S-01","deger":21.4}
{"sensor":"S-01","deger":21.4}
true
2

Özellik okunabilir durumdadır ve in işleci onu görür; ama Object.keys, Object.entries, JSON.stringify, nesne yayma ve Object.assign onu atlar. Object.getOwnPropertyNames ise sayılabilirliğe bakmaz, bu yüzden gizli kalan özellikleri denetlemenin yolu odur.

Buradan pratik bir kural çıkar: veriye eşlik eden ama verinin parçası olmayan bilgiler — kaynak dosya adı, iç sayaçlar, önbellek alanları — sayılamaz tanımlanırsa dizileştirme ve kopyalama işlemlerine kendiliğinden karışmaz. Aynı sonucu simge (symbol) anahtarlı özellikler de verir; onlar da Object.keys ve JSON.stringify çıktılarına girmez.

Erişimci Özellikler

İkinci tanımlayıcı türünde value ve writable yerine get ve set fonksiyonları bulunur. Böyle bir özelliğe erişimci (accessor) denir: okuma get fonksiyonunu, yazma set fonksiyonunu çalıştırır.

Erişimcinin değeri, hesaplanan bir büyüklüğü özellik gibi sunmasıdır. Ölçüm kaydında okunan değer, ham değer ile kalibrasyon kaymasının toplamıdır; bunu bir yöntem yerine erişimci yapmak, çağıranı hesabın varlığından habersiz bırakır.

const olcumDavranisi = {};

Object.defineProperty(olcumDavranisi, "okunanDeger", {
  get() {
    return this.deger + this.kayma;
  },
  set(yeniDeger) {
    if (typeof yeniDeger !== "number" || Number.isNaN(yeniDeger)) {
      throw new TypeError("okunan deger sayi olmalidir");
    }
    this.deger = yeniDeger - this.kayma;
  },
  enumerable: false,
  configurable: true,
});

function olcumKaydi(sensor, deger, kayma) {
  const kayit = Object.create(olcumDavranisi);
  kayit.sensor = sensor;
  kayit.deger = deger;
  kayit.kayma = kayma;
  return kayit;
}

const kayit = olcumKaydi("S-01", 21.4, -0.4);

console.log(kayit.okunanDeger);
kayit.okunanDeger = 25;
console.log(kayit.deger);
console.log(kayit.okunanDeger);

try {
  kayit.okunanDeger = "yirmi";
} catch (hata) {
  console.log(`${hata.constructor.name}: ${hata.message}`);
}

console.log(Object.hasOwn(kayit, "okunanDeger"));
console.log(Object.hasOwn(olcumDavranisi, "okunanDeger"));
console.log(JSON.stringify(kayit));
21
25.4
25
TypeError: okunan deger sayi olmalidir
false
true
{"sensor":"S-01","deger":25.4,"kayma":-0.4}

Erişimci prototipte tanımlıdır; kaydın kendi özelliği değildir. Buna karşın get ve set gövdelerindeki this, erişimin yapıldığı nesneye — yani kaydın kendisine — bağlanır. Zincirde bulunan bir fonksiyon, çağrıldığı nesnenin verisiyle çalışır; bu kuralın tamamı sonraki konunun ilk dersinde ele alınacaktır.

Yazıcı fonksiyon, Programlama Temelleri kursundaki Nesneye Dayalı Programlamaya Giriş dersinde tanıtılan sınıf değişmezini korumanın doğrudan yoludur: geçersiz değer nesneye hiç girmez. Ayrıca erişimci sayılamaz tanımlandığı için dizileştirmeye girmez; son satırda yalnızca ham veri alanları görünür. Bu, hesaplanmış değerlerin depolanan veriyle karışmamasını sağlar.

Sınıf gövdesindeki get ve set sözcükleri bu tanımın kısaltmasıdır; ürettikleri erişimci de prototype nesnesine, sayılamaz olarak yazılır.

Tanımlayıcıları Koruyan Kopyalama

Bir nesneyi kopyalamanın alışılmış yolları tanımlayıcıları taşımaz. Nesne yayma ve Object.assign, kaynaktaki her özelliği okur ve hedefe sıradan bir veri özelliği olarak yazar. Erişimciler bu işlemde donmuş değere dönüşür.

const kaynak = {
  sensor: "S-01",
  deger: 21.4,
  get etiket() {
    return `${this.sensor}/${this.deger}`;
  },
};

const yayilmis = { ...kaynak };
const tanimlaKopya = Object.defineProperties(
  Object.create(Object.getPrototypeOf(kaynak)),
  Object.getOwnPropertyDescriptors(kaynak),
);

console.log(kaynak.etiket);
console.log(yayilmis.etiket);
console.log(tanimlaKopya.etiket);

kaynak.deger = 30;
yayilmis.deger = 30;
tanimlaKopya.deger = 30;

console.log(kaynak.etiket);
console.log(yayilmis.etiket);
console.log(tanimlaKopya.etiket);

console.log(typeof Object.getOwnPropertyDescriptor(yayilmis, "etiket").get);
console.log(typeof Object.getOwnPropertyDescriptor(tanimlaKopya, "etiket").get);
S-01/21.4
S-01/21.4
S-01/21.4
S-01/30
S-01/21.4
S-01/30
undefined
function

Yayma ile üretilen kopyada etiket artık hesaplanmaz; kopyalama anındaki metni taşıyan sabit bir veri özelliğidir, bu yüzden deger değiştiğinde eskisini göstermeye devam eder. Object.getOwnPropertyDescriptors ile Object.defineProperties birlikte kullanıldığında hem erişimciler hem de bayraklar korunur; Object.create çağrısı da prototip bağını taşır. Kopyalamanın genel sorunu — hangi bilginin taşındığı, hangisinin düştüğü — kursun son dersinde bütünüyle ele alınacaktır.

Özet

  • Her özellik, değerinin yanında writable, enumerable, configurable bayraklarını taşır; Object.getOwnPropertyDescriptor bunları okur.
  • Object.defineProperty çağrısında belirtilmeyen bayraklar kapalı sayılır; atama ile üretilen özelliklerde ise üçü de açıktır.
  • Yazılamayan özelliğe atama katı kipte hata yükseltir, katı olmayan kipte sessizce başarısız olur; configurable: false geri alınamaz.
  • Sayılamaz özellikler Object.keys, Object.entries, JSON.stringify, yayma ve Object.assign sonuçlarına girmez; Object.getOwnPropertyNames ile görünür.
  • Erişimci özellikte okuma ve yazma birer fonksiyon çalıştırır; yazıcı, geçersiz değerin nesneye girmesini engelleyerek sınıf değişmezini korur.
  • Yayma ve Object.assign tanımlayıcıları taşımaz; erişimciyi sabit değere çevirir.

Sonraki Adım

writable: false tek bir özelliği kilitler. Bir nesnenin tamamının değişmesini önlemek, ona yeni özellik eklenmesini yasaklamak ya da yalnızca var olan alanların değerini değiştirilebilir bırakmak için özellik başına tanım yazmak zahmetlidir. Sonraki ders nesnenin bütününe uygulanan dondurma ve mühürleme işlemlerini, bunların ne kadar derine indiğini ve değişiklik gerektiğinde yeni nesne üretmenin kalıplarını ele alacak.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat