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
callveapplybağ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
nullveundefinedbağ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.
bindyeni 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.