İçeriğe geç
academia.sh

Ders 06 / 17

this Bağlanması

Bağlamın çağrı yerinde belirlenmesi; genel, örtülü, açık ve yapıcı bağlanma kuralları, aralarındaki öncelik sırası ve yöntemin nesnesinden kopması.

İçindekiler

Prototip Modeli konusu boyunca yöntemler this üzerinden çalıştı ve bu, sanki fonksiyonun tanımlandığı nesneyi bildiği izlenimini verdi. İzlenim yanlıştır. Önceki derste Object.assign(kopya, this, ...) çağrısında olduğu gibi, prototipteki bir fonksiyon zincirinde hiç bulunmadığı bir nesne üzerinde de çalışabilir.

Kural şudur: this değeri fonksiyonun tanımlandığı yere değil, çağrıldığı biçime bakılarak belirlenir. Aynı fonksiyon, dört farklı çağrı biçiminde dört farklı değere bağlanır. Bu ders dördünü ayırt eder ve aralarındaki öncelik sırasını kurar.

Genel Bağlanma

En yalın çağrı biçimi, fonksiyonun hiçbir nesneye iliştirilmeden çağrılmasıdır. Katı kipte this değeri undefined olur.

function bagiOku() {
  return this;
}

console.log(bagiOku() === undefined);
console.log(typeof globalThis);

const katiOlmayanBagiOku = new Function("return this;");
console.log(katiOlmayanBagiOku() === globalThis);
console.log(katiOlmayanBagiOku() === undefined);
true
object
true
false

Katı olmayan kipte aynı çağrı this değerini genel nesneye bağlar; globalThis bu nesnenin standart adıdır. Fark önemlidir, çünkü yanlışlıkla nesnesiz çağrılan bir yöntem katı kipte hemen hata verirken, katı olmayan kipte genel nesneye sessizce özellik yazar. Modül dosyaları ve sınıf gövdeleri katı kipte çalıştığı için, ölçüm kaydı örneklerinin tamamında geçerli olan davranış birincisidir.

Örtülü Bağlanma

Fonksiyon bir nesnenin özelliği üzerinden çağrılırsa this, o nesneye bağlanır. Belirleyen şey çağrı yerindeki nokta işlecinin solundaki nesnedir.

const kayit = {
  sensor: "S-01",
  deger: 21.4,
  zaman: 1000,
  bicimle() {
    return `${this.sensor}@${this.zaman}: ${this.deger}`;
  },
};

console.log(kayit.bicimle());

const kopukBicimle = kayit.bicimle;
try {
  console.log(kopukBicimle());
} catch (hata) {
  console.log(`kopuk cagri: ${hata.constructor.name}`);
}

const kayitlar = [kayit];
try {
  console.log(kayitlar.map(kayit.bicimle)[0]);
} catch (hata) {
  console.log(`geri cagri: ${hata.constructor.name}`);
}

console.log(kayitlar.map((k) => k.bicimle())[0]);

const olcum = {
  sensor: "S-DIS",
  kaynak: {
    sensor: "S-IC",
    bicimle() {
      return `bag: ${this.sensor}`;
    },
  },
};
console.log(olcum.kaynak.bicimle());
S-01@1000: 21.4
kopuk cagri: TypeError
geri cagri: TypeError
S-01@1000: 21.4
bag: S-IC

İkinci ve üçüncü satırlar dilin en sık karşılaşılan tuzağını gösterir. kayit.bicimle ifadesi bir fonksiyon değeri verir; nesneyle bağı bu değerin içinde taşınmaz. Değer bir değişkene atandığında ya da geri çağrı olarak aktarıldığında çağrı yeri değişir ve bağlanma genel kurala düşer. Katı kipte this undefined olduğu için this.sensor okuması hata yükseltir.

Bu, Programlama Temelleri kursundaki Değer ve Referans ile Aktarım dersindeki gözlemin bir sonucudur: aktarılan şey fonksiyonun kendisidir, bulunduğu bağlam değil. Dördüncü satırdaki çözüm en dolaysızıdır — çağrıyı nesne üzerinden yapan bir sarmalayıcı yazmak.

Son satır zincir uzunluğunun rol oynamadığını gösterir: yalnızca son halka sayılır. olcum.kaynak.bicimle() çağrısında this, kaynak nesnesidir; dıştaki olcum hiç dikkate alınmaz.

Açık Bağlanma

call, apply ve bind işlemleri bağlamı çağrı yerinde açıkça belirtir. Ayrıntıları sonraki dersin konusudur; burada yalnızca kuralın bir üçüncü biçim olduğu gösterilir.

const olcumDavranisi = {
  bicimle() {
    return `${this.sensor}@${this.zaman}: ${this.deger}`;
  },
};

const zincirdeki = Object.create(olcumDavranisi);
zincirdeki.sensor = "S-01";
zincirdeki.deger = 21.4;
zincirdeki.zaman = 1000;

const yabanci = { sensor: "S-99", deger: 5.5, zaman: 7 };

console.log(zincirdeki.bicimle());
console.log(olcumDavranisi.bicimle.call(yabanci));
console.log(zincirdeki.bicimle.call(yabanci));
console.log(Object.getPrototypeOf(yabanci) === olcumDavranisi);
S-01@1000: 21.4
S-99@7: 5.5
S-99@7: 5.5
false

Son satır dersin başındaki savı doğrular: yabanci nesnesinin zincirinde ölçüm davranışı yoktur, buna karşın aynı fonksiyon onun verisiyle sorunsuz çalışır. Prototip zinciri fonksiyonun nerede bulunacağını belirler; this ise hangi veri üzerinde çalışacağını. İkisi birbirinden bağımsızdır.

Yapıcı Bağlanma

new işleci, ikinci derste sıralanan adımların ikincisinde this değerini yeni yaratılan nesneye bağlar.

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

const yapiciyla = new OlcumKaydi("S-01", 21.4);
console.log(yapiciyla.bicimle());
console.log(Object.getPrototypeOf(yapiciyla) === OlcumKaydi.prototype);

try {
  OlcumKaydi("S-02", 19.8);
} catch (hata) {
  console.log(`new olmadan: ${hata.constructor.name}`);
}

class SinifKaydi {
  constructor(sensor) {
    this.sensor = sensor;
  }
  bicimle() {
    return `sinif: ${this.sensor}`;
  }
}

const ornek = new SinifKaydi("S-03");
console.log(ornek.bicimle());

const kopuk = ornek.bicimle;
try {
  kopuk();
} catch (hata) {
  console.log(`sinif yontemi kopuk: ${hata.constructor.name}`);
}
S-01: 21.4
true
new olmadan: TypeError
sinif: S-03
sinif yontemi kopuk: TypeError

Üçüncü satır, yapıcı işlevin new olmadan çağrılmasının katı kipte nasıl sonuçlandığını gösterir: this undefined olduğu için ilk atama hata yükseltir. Sınıf sözdiziminin bu çağrıyı baştan yasaklaması, tam da bu sessiz hatayı önlemek içindir.

Son satır önemli bir noktayı yineler: sınıf sözdizimi kopma sorununu çözmez. Sınıf yöntemleri de prototipte duran sıradan fonksiyonlardır ve nesnelerinden ayrıldıklarında aynı biçimde bağlarını yitirirler.

Geri Çağırmada Bağlam Kaybı

Dört kural, hataların çıktığı yeri de açıklar. Bir yöntem, gövdesinde başka bir işleve fonksiyon geçirdiğinde o fonksiyon ayrı bir çağrı olur; dış yöntemin bağlamı ona taşınmaz.

const toplayici = {
  birim: "C",
  olcumler: [21.4, 22.8, 20.1],
  bicimliListe() {
    return this.olcumler.map(function (deger) {
      return `${deger}${this.birim}`;
    });
  },
  bicimliListeThisArg() {
    return this.olcumler.map(function (deger) {
      return `${deger}${this.birim}`;
    }, this);
  },
};

try {
  console.log(toplayici.bicimliListe());
} catch (hata) {
  console.log("geri çağırmada:", hata.constructor.name, "-", hata.message);
}

console.log(toplayici.bicimliListeThisArg());
geri çağırmada: TypeError - Cannot read properties of undefined (reading 'birim')
[ '21.4C', '22.8C', '20.1C' ]

Birinci çağrıda geri çağırma işlevi genel bağlanmaya düşer; katı kipte this tanımsız olduğu için özellik okuması hata verir. Dizi yöntemlerinin bir bölümü ikinci bir parametre alır ve geri çağırmayı verilen nesneye bağlar; ikinci çağrı bunu kullanır.

Bu parametre her arayüzde bulunmaz. Genel çözüm iki adımdan birindedir: bağlamı açık bağlanmayla sabitlemek (sonraki ders) ya da kendi this bağını kurmayan bir fonksiyon biçimi kullanmak (üçüncü ders). Sorunun kaynağı, çağrı yerinin fonksiyonun tanımlandığı yerden bağımsız olmasıdır.

Öncelik Sırası

Birden fazla kural aynı anda geçerli göründüğünde hangisinin kazandığı sabittir: yapıcı bağlanma, açık bağlanmayı; açık bağlanma, örtülü bağlanmayı; örtülü bağlanma, genel bağlanmayı geçersiz kılar.

function sensoruYaz(sensor) {
  this.sensor = sensor;
  return this;
}

const hedef = { sensor: "baslangic" };
const digerHedef = { sensor: "diger" };

const bagli = sensoruYaz.bind(hedef);

bagli("acik-bagli");
console.log(hedef.sensor);

digerHedef.calistir = bagli;
digerHedef.calistir("ortulu-denemesi");
console.log(hedef.sensor);
console.log(digerHedef.sensor);

const uretilen = new bagli("yapici");
console.log(uretilen.sensor);
console.log(hedef.sensor);
console.log(Object.getPrototypeOf(uretilen) === sensoruYaz.prototype);

const nesne = {
  sensor: "ortulu",
  oku() {
    return this.sensor;
  },
};
console.log(nesne.oku());
console.log(nesne.oku.call(digerHedef));
acik-bagli
ortulu-denemesi
diger
yapici
ortulu-denemesi
true
ortulu
diger

İkinci ve üçüncü satırlar açık bağlanmanın örtülü bağlanmayı yendiğini söyler: bağlı fonksiyon bir nesnenin özelliği olarak çağrılsa bile yazma işlemi hedef üzerinde gerçekleşir, digerHedef değişmez. Dördüncü ve beşinci satırlar yapıcı bağlanmanın açık bağlanmayı yendiğini gösterir: new ile çağrıldığında bağlı hedef göz ardı edilir, yeni nesne kullanılır ve zincir yapıcının prototype nesnesine kurulur.

Sıra tek cümleyle özetlenebilir: new > bind/call/apply > nokta işleci > hiçbiri. Beşinci bir durum daha vardır — hiçbir kurala uymayan, kendi this değeri olmayan fonksiyonlar — ve iki ders sonra ele alınacaktır.

Özet

  • this değeri fonksiyonun tanımlandığı yere değil, çağrıldığı biçime bakılarak belirlenir.
  • Genel bağlanmada this, katı kipte undefined, katı olmayan kipte genel nesnedir.
  • Örtülü bağlanmada nokta işlecinin solundaki nesne bağlanır; yalnızca son halka sayılır.
  • Yöntem bir değişkene atandığında ya da geri çağrı olarak aktarıldığında bağı kopar; sınıf yöntemleri bu davranışta ayrıcalıklı değildir.
  • Prototip zinciri fonksiyonun nerede bulunacağını, this hangi veri üzerinde çalışacağını belirler; ikisi bağımsızdır.
  • Öncelik sırası: yapıcı bağlanma, açık bağlanma, örtülü bağlanma, genel bağlanma.

Sonraki Adım

Bu derste açık bağlanma yalnızca bir kural olarak tanıtıldı; onu kuran üç işlemin farkları ele alınmadı. call ile apply argümanları nasıl aktarır, bind neden kalıcı bir bağ üretir ve bu işlemler bir nesnenin zincirinde olmayan bir yöntemi ona nasıl ödünç verir? Sonraki ders bu üç işlemi ve fonksiyon ödünç alma kalıbını inceleyecek.

İ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