İçeriğe geç
academia.sh

Ders 07 / 17

call, apply ve bind

Bağlamı çağrı yerinde belirleyen üç işlem, argüman aktarma biçimleri, fonksiyon ödünç alma kalıbı, kısmi uygulama ve kalıcı bağın kimlik maliyeti.

İçindekiler

Önceki ders açık bağlanmayı bir kural olarak tanıttı ve şunu gösterdi: prototip zinciri fonksiyonun nerede bulunacağını, this hangi veri üzerinde çalışacağını belirler. İkisi bağımsız olduğu için, bir nesne hiç tanımadığı bir yöntemle çalıştırılabilir.

Bu bağımsızlığı kullanan üç işlem vardır. call ve apply bağlamı yalnızca o çağrı için belirler ve fonksiyonu hemen çalıştırır; bind ise bağı kalıcı biçimde taşıyan yeni bir fonksiyon üretir. Ders üçünün farklarını, ödünç alma kalıbını ve kalıcı bağın bedelini ele alır.

Argümanların Aktarılması

call ile apply arasındaki tek fark argümanların nasıl verildiğidir: call onları tek tek alır, apply bir dizi olarak.

const olcumDavranisi = {
  ozetle(onEk, ayrac) {
    return `${onEk}${ayrac}${this.sensor}${ayrac}${this.deger}`;
  },
};

const kayit = { sensor: "S-01", deger: 21.4 };
const argumanlar = ["olcum", " | "];

console.log(olcumDavranisi.ozetle.call(kayit, "olcum", " | "));
console.log(olcumDavranisi.ozetle.apply(kayit, argumanlar));
console.log(olcumDavranisi.ozetle.call(kayit, ...argumanlar));

const degerler = [21.4, 19.8, 25.1, 18.2];
console.log(Math.max.apply(null, degerler));
console.log(Math.max(...degerler));

try {
  olcumDavranisi.ozetle.call(null, "olcum", "-");
} catch (hata) {
  console.log(`null baglam: ${hata.constructor.name}`);
}
olcum | S-01 | 21.4
olcum | S-01 | 21.4
olcum | S-01 | 21.4
25.1
25.1
null baglam: TypeError

Yayma işleci dile girdikten sonra apply işleminin argüman açma işlevi büyük ölçüde gereksizleşmiştir; üçüncü ve beşinci satırlar aynı işi yayma ile yapar. apply, argüman listesi çalışma zamanında hesaplanan ve dizi olarak elde tutulan durumlarda hâlâ doğrudan bir yol sunar.

Son satır bir ayrıntıyı sabitler: katı kipte null ya da undefined bağlamı genel nesneye çevrilmez, olduğu gibi aktarılır. Bu yüzden Math.max.apply(null, ...) çalışır — Math.max this değerini hiç kullanmaz — ama this okuyan bir fonksiyon aynı çağrıda hata verir.

Fonksiyon Ödünç Alma

Açık bağlanmanın asıl kullanımı fonksiyon ödünç alma (function borrowing): bir nesnenin zincirinde bulunmayan bir yöntemi, o nesne üzerinde çalıştırmak.

const diziBenzeri = { 0: "S-01", 1: "S-02", 2: "S-03", length: 3 };

console.log(Array.prototype.join.call(diziBenzeri, ";"));
console.log(Array.prototype.map.call(diziBenzeri, (a) => a.toLowerCase()).join(","));
console.log(Array.from(diziBenzeri).join(","));
console.log(Array.isArray(diziBenzeri));

const sozluk = Object.create(null);
sozluk.sensor = "S-01";

console.log(typeof sozluk.hasOwnProperty);
console.log(Object.prototype.hasOwnProperty.call(sozluk, "sensor"));
console.log(Object.hasOwn(sozluk, "sensor"));

console.log(Object.prototype.toString.call([1, 2]));
console.log(Object.prototype.toString.call(null));
console.log(Object.prototype.toString.call(new Date(0)));
console.log(Object.prototype.toString.call({ sensor: "S-01" }));
S-01;S-02;S-03
s-01,s-02,s-03
S-01,S-02,S-03
false
undefined
true
true
[object Array]
[object Null]
[object Date]
[object Object]

İlk üç satır dizi benzeri (array-like) nesneleri gösterir: sayısal anahtarlar ve bir length özelliği taşıyan, ama zincirinde Array.prototype bulunmayan nesneler. Dizi yöntemleri yalnızca bu iki şeye baktığı için ödünç alınabilirler. Dördüncü satır nesnenin gerçekten dizi olmadığını doğrular. Array.from aynı işi daha okunur biçimde yapar ve ödünç almaya çoğu durumda tercih edilir.

Beşinci ve altıncı satırlar ilk dersteki prototipsiz sözlüğe bağlanır. Böyle bir nesnede hasOwnProperty yoktur, çünkü zinciri yoktur; sorgu ancak Object.prototype üzerinden ödünç alınarak yapılabilir. Object.hasOwn bu ödünç almanın dile eklenmiş biçimidir ve aynı sonucu daha az dolayımla verir.

Son dört satır, Object.prototype.toString yönteminin her değer türü için ayırt edici bir etiket üretmesinden yararlanır. typeof işlecinin ayıramadığı durumları — dizi ile nesne, null ile nesne — bu ödünç alma ayırır.

Kalıcı Bağ ve Kısmi Uygulama

bind, fonksiyonu çalıştırmaz; bağlamı ve isteğe bağlı olarak baştaki argümanları sabitlenmiş yeni bir fonksiyon döndürür. Argümanların önden sabitlenmesine kısmi uygulama (partial application) denir.

function kalibreEt(kayma, carpan, hamDeger) {
  return (hamDeger + kayma) * carpan;
}

const sensorS01 = kalibreEt.bind(null, -0.4, 1.02);

console.log(kalibreEt(-0.4, 1.02, 21.4).toFixed(3));
console.log(sensorS01(21.4).toFixed(3));
console.log(sensorS01(19.8).toFixed(3));

console.log(kalibreEt.length);
console.log(sensorS01.length);
console.log(sensorS01.name);

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

const bagliBicimle = kayit.bicimle.bind(kayit);
const yeniden = bagliBicimle.bind({ sensor: "S-99", deger: 0 });

console.log(bagliBicimle());
console.log(yeniden());
21.420
21.420
19.788
3
1
bound kalibreEt
S-01: 21.4
S-01: 21.4

Kısmi uygulama, her sensör için ayrı bir kalibrasyon fonksiyonu üretmenin doğrudan yoludur; sabit parametreler bir kez verilir, değişen parametre her çağrıda. Fonksiyonun length özelliği sabitlenen argüman sayısı kadar azalır, name özelliği ise türetilmiş olduğunu belirtir.

Son iki satır kritik bir kuralı gösterir: bağlı fonksiyon yeniden bağlanamaz. İkinci bind çağrısı yeni bir fonksiyon üretir ama içteki bağ değişmez, bu yüzden sonuç değişmemiştir. Aynı kural önceki dersteki öncelik sırasının bir parçasıydı; new bu kuralın tek istisnasıdır.

Kayan noktalı çıktıların toFixed ile biçimlendirilmesi bilinçlidir: (21.4 - 0.4) * 1.02 ifadesinin doğrudan yazdırılması, ikilik kesir gösteriminden gelen basamakları da gösterirdi. Bilgisayarlar Nasıl Çalışır kursundaki Kayan Noktalı Sayılar dersi bu davranışı açıklar.

Geri Çağrılarda Bağı Korumak

Önceki dersteki kopma sorununun en sık görüldüğü yer, yöntemin bir geri çağrı olarak aktarılmasıdır. Üç çözüm vardır ve üçü de aynı sonucu verir.

class OlcumToplayici {
  constructor(sensor) {
    this.sensor = sensor;
    this.degerler = [];
  }
  ekle(deger) {
    this.degerler.push(deger);
  }
  ozet() {
    return `${this.sensor}: ${this.degerler.join(",")}`;
  }
}

const toplayici = new OlcumToplayici("S-01");
const gelenler = [21.4, 19.8, 25.1];

try {
  gelenler.forEach(toplayici.ekle);
} catch (hata) {
  console.log(`baglamsiz: ${hata.constructor.name}`);
}

gelenler.forEach(toplayici.ekle.bind(toplayici));
console.log(toplayici.ozet());

const ikinci = new OlcumToplayici("S-02");
gelenler.forEach(ikinci.ekle, ikinci);
console.log(ikinci.ozet());

const ucuncu = new OlcumToplayici("S-03");
gelenler.forEach((deger) => ucuncu.ekle(deger));
console.log(ucuncu.ozet());
baglamsiz: TypeError
S-01: 21.4,19.8,25.1
S-02: 21.4,19.8,25.1
S-03: 21.4,19.8,25.1

Birinci çözüm bind ile kalıcı bağ üretir. İkincisi, forEach gibi bazı dizi yöntemlerinin ikinci parametre olarak aldığı bağlam argümanını kullanır — bu seçenek map, filter, some ve every yöntemlerinde de bulunur, ama her yüksek mertebeden fonksiyonda bulunmaz. Üçüncüsü çağrıyı bir ok fonksiyonuyla sarar ve bağlanmayı hiç gerektirmez; nedeni sonraki dersin konusudur.

Bağın Kimlik Maliyeti

bind her çağrıldığında yeni bir fonksiyon nesnesi üretir. Bu, kimlik karşılaştırmasına dayanan her yerde sonuç doğurur.

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

const birinci = kayit.bicimle.bind(kayit);
const ikinci = kayit.bicimle.bind(kayit);

console.log(birinci() === ikinci());
console.log(birinci === ikinci);
console.log(birinci === kayit.bicimle);

class Dinleyici {
  constructor(ad) {
    this.ad = ad;
    this.isle = this.isle.bind(this);
  }
  isle(olay) {
    return `${this.ad} <- ${olay}`;
  }
}

const a = new Dinleyici("A");
const b = new Dinleyici("B");

console.log(a.isle("olcum"));
console.log(Object.hasOwn(a, "isle"));
console.log(a.isle === b.isle);
console.log(Dinleyici.prototype.isle === Object.getPrototypeOf(a).isle);
console.log(a.isle === Dinleyici.prototype.isle);
true
false
false
A <- olcum
true
false
true
false

İki bind çağrısı aynı sonucu üreten ama birbirine eşit olmayan iki fonksiyon verir. Bir olay dinleyicisini kaydettikten sonra kaldırmak için aynı fonksiyon başvurusu gerektiğinden, bind sonucunun saklanması zorunludur; her seferinde yeniden bağlanan bir fonksiyon kaldırılamaz.

İkinci bölüm, sınıf yapıcısında yapılan bağlamanın prototip modeline etkisini gösterir. this.isle = this.isle.bind(this) satırı, prototipteki yöntemi gölgeleyen bir kendi özellik yaratır. Sonuç: her örnek kendi bağlı fonksiyonunu taşır, örnekler arasındaki paylaşım kaybolur, bellekteki fonksiyon sayısı örnek sayısıyla artar. Prototipteki asıl tanım yerinde durur ama artık kullanılmaz.

Bu, kursun ilk dersindeki ödünleşimin geri dönüşüdür: paylaşılan davranış zincirde durduğunda ucuzdur, örneğe kopyalandığında pahalı ama bağlamı garantilidir. Kapanışlar dersi bu ödünleşimi tekrar ve daha genel biçimde ele alacaktır.

Özet

  • call ve apply bağlamı yalnızca o çağrı için belirler; fark yalnızca argümanların tek tek mi dizi olarak mı verildiğidir.
  • Katı kipte null ve undefined bağlamı genel nesneye çevrilmez.
  • Fonksiyon ödünç alma, bir yöntemi zincirinde bulunmadığı bir nesne üzerinde çalıştırır; dizi benzeri nesneler ve prototipsiz sözlükler bunun tipik durumlarıdır.
  • bind yeni bir fonksiyon üretir, argümanları kısmen sabitleyebilir ve ürettiği bağ yeniden bağlamayla değiştirilemez.
  • Her bind çağrısı yeni bir kimlik üretir; kaldırılabilir dinleyicilerde sonucun saklanması gerekir.
  • Yapıcıda yapılan bağlama, prototipteki yöntemi örnek başına bir kopyayla gölgeler ve paylaşımı ortadan kaldırır.

Sonraki Adım

Önceki bölümde geri çağrı sorununun üçüncü çözümü bir ok fonksiyonuydu ve hiçbir bağlama işlemi gerektirmedi. Bunun nedeni, ok fonksiyonlarının this değerini çağrı yerinden almamasıdır — kendilerine ait bir bağlamları yoktur. Sonraki ders bu davranışı, sağladığı kolaylığı ve ok fonksiyonunun yöntem olarak kullanılamadığı durumları 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