Ders 01 / 14
Modül İhtiyacı
Genel kapsamın paylaşılmasından doğan ad çakışması sorunu, kapanışla kurulan ad alanı kalıbı ve modül kapsamının bu soruna verdiği yanıt.
İçindekiler
Eşzamansız JavaScript ve Çalışma Zamanı kursu, tek bir programın nasıl yürüdüğünü ve sorunlarının nasıl tanılandığını ele aldı. Bu kurs bir üst düzeye çıkar: bir program tek dosyada durmadığında, parçaların birbirini nasıl bulduğu ve kimin neyi görebildiği sorusuna.
Soru masum görünür. Kodu iki dosyaya bölmek, dosyaları birbiri ardına yüklemekten ibaret sanılır. Bu varsayımın nerede kırıldığını görmek, modül kavramının neden dile eklendiğini de açıklar.
Genel Kapsam Ortak Bir Kaynaktır
Dilin ilk yürütme birimi betik (script) idi: kaynak metin ayrıştırılır ve en üst düzeydeki bildirimleri genel kapsama yazılır. İki ayrı dosya, aynı çalışma zamanında art arda yüklendiğinde ayrı bir dünya kurmaz; ikisi de aynı genel nesneye yazar.
Bu davranış, çalışma zamanının betik değerlendirme arayüzüyle doğrudan gözlenebilir:
// dosya: betikler.mjs import vm from 'node:vm'; // İki ayrı "betik", aynı genel kapsamda çalıştırılıyor. vm.runInThisContext('var surum = "cizim-1.0"; function bicimle(d) { return "cizim: " + d; }'); vm.runInThisContext('var surum = "rapor-2.0"; function bicimle(d) { return "rapor: " + d; }'); console.log(globalThis.surum); console.log(globalThis.bicimle('x'));
$ node betikler.mjs rapor-2.0 rapor: x
İlk betiğin surum değişkeni ve bicimle fonksiyonu kayboldu. Hata yok, uyarı yok:
ikinci bildirim birincinin üzerine yazdı. Program çalışmaya devam eder ve yanlış sonuç
üretir.
Sorunun büyüklüğü dosya sayısıyla değil, ad sayısıyla artar. Yirmi dosyanın her biri beş genel ad tanımlıyorsa yüz adlık ortak bir havuz oluşur ve bu havuzun çakışmasız kalması yalnızca yazarların dikkatine bağlıdır. Programlama Temelleri kursunda kurulan kapsam kavramı burada işletim düzeyinde bir soruna dönüşür: paylaşılan yazılabilir bir ad alanı, yalıtımsız bir kaynaktır.
İkinci sorun sıralamadır. Bir dosya diğerinin tanımladığı fonksiyonu kullanıyorsa yükleme sırası önemlidir, ama bu sıra kodun kendisinde yazılı değildir; onu yükleyen tarafın bilmesi gerekir. Bağımlılık bilgisi kaynakta değil, kaynağın dışında durur.
Kapanışla Kurulan Ad Alanı
Dilde modül sözdizimi yokken bu iki soruna verilen yanıt, kapanışların (closure) kapsam yalıtımı özelliğini kullanmaktı. Bir fonksiyon tanımlanıp hemen çağrılır, iç adlar o fonksiyonun kapsamında kalır, dışarıya yalnızca seçilmiş bir nesne verilir:
// dosya: iife.mjs var Olcer = (function () { var ayirac = /\s+/; function sozcukler(metin) { return metin.trim().split(ayirac); } return { sozcukler: sozcukler }; })(); console.log(Olcer.sozcukler('bir iki uc')); console.log(typeof ayirac);
$ node iife.mjs [ 'bir', 'iki', 'uc' ] undefined
ayirac dışarıdan görünmez; yalnızca Olcer adı genel kapsama yazılır. Bu kalıp, JavaScript’te
Nesneler ve Fonksiyonlar kursundaki hemen çağrılan fonksiyon kalıbının doğrudan
uygulamasıdır ve gerçek bir kazanç sağlar: yüz genel ad yerine bir genel ad.
Kazandırdığı kadarını da kaybettirir. Ad alanı nesnesinin kendisi hâlâ genel kapsamdadır
ve çakışabilir. Bağımlılıklar yine sözleşmeyle taşınır: Olcer, kendisinden önce bir
başka ad alanının yüklenmiş olmasını bekliyorsa bunu yazılı olarak bildiremez. Dahası
hangi adın nereden geldiği, kaynağa bakılarak değil, tüm dosyalar taranarak bulunur.
Modül Kapsamı
Modül, bu sorunları dilin ayrıştırma düzeyinde çözer. Bir kaynak metin modül (module) olarak ayrıştırıldığında en üst düzeydeki bildirimleri genel kapsama değil, o modüle özgü bir kapsama yazılır:
// dosya: kapsam.mjs const surum = 'metin-olcer-1.0'; function bicimle(d) { return 'olcer: ' + d; } console.log(typeof globalThis.surum); // modül kapsamındaki ad genel nesnede yok console.log(typeof globalThis.bicimle); console.log(this); // modülün en üst düzeyinde this
$ node kapsam.mjs undefined undefined undefined
Üç çıktı da aynı şeyi söyler: modülün en üst düzeyi, genel kapsam değildir. surum ve
bicimle adları dışarıdan görünmez; en üst düzeydeki this genel nesneye değil,
tanımsıza bağlanır.
Ad alanı kalıbıyla arasındaki fark, yalıtımın kaynağıdır. Kapanış kalıbında yalıtım programcının kurduğu bir yapıydı ve bozulabilirdi; modülde yalıtım ayrıştırıcının kararıdır ve bozulamaz.
Betik ile Modülün Ayrımı
Aynı kaynak metin, iki farklı kural kümesiyle ayrıştırılabilir. Ayrım yalnızca kapsamla sınırlı değildir:
| Ölçüt | Betik | Modül |
|---|---|---|
| En üst düzey bildirimler | Genel kapsama yazılır | Modül kapsamında kalır |
En üst düzeydeki this |
Genel nesne | Tanımsız |
| Katı kip | Açıkça istenmelidir | Her zaman etkindir |
| İçe/dışa aktarım sözdizimi | Geçersiz | Geçerli |
| Değerlendirme zamanı | Karşılaşıldığı anda | Bağımlılıklar çözüldükten sonra |
Son satır belirleyicidir ve sonraki derste ayrıntılandırılacaktır. Bir metnin hangi kurala göre ayrıştırılacağı, çalışma zamanının okuduğu bir işaretle belirlenir: dosya uzantısı, bildirim dosyasındaki tip alanı ya da betiği yükleyen tarafın verdiği tür bilgisi. Aynı karakter dizisi, bu işarete göre farklı anlam kazanır.
Bir Modül Sisteminden Beklenenler
Bir modül sisteminin çözmesi gereken sorunlar, yukarıdaki iki denemeden çıkarılabilir:
Yalıtım. Modülün iç adları dışarıdan görünmemeli; dışarıya ne verdiğini modülün kendisi seçmeli.
Açık arayüz. Bir modülün neye bağlı olduğu ve ne sunduğu, kaynağın kendisinde yazılı olmalı. Bağımlılık bilgisi yükleyicinin belleğinde değil, kodda durmalı.
Belirlenimci çözümleme. Bir bağımlılık adı, her ortamda aynı dosyaya karşılık gelmeli. Ad çözümleme kuralı yazılı ve öngörülebilir olmalı.
Tek değerlendirme. Aynı modül birden çok yerden istendiğinde bir kez değerlendirilmeli; tuttuğu durum tüm kullanıcılar için aynı olmalı. Bu güvence, Bilgisayarlar Nasıl Çalışır kursundaki bağlama ve yükleme dersinde tanıtılan simge çözümlemesinin dil düzeyindeki karşılığıdır: bir ad, tek bir tanıma bağlanır.
Bu dört güvence, kurs boyunca incelenecek her mekanizmanın ölçütüdür. Paket çözümlemesi üçüncüsünün, önbellekleme dördüncüsünün gerçekleştirimidir.
Kursun Örneği
Kurs boyunca tek bir somut örnek geliştirilecektir: metin-olcer adlı küçük bir kütüphane
paketi. İşi, verilen metnin sözcük ve cümle sayısını, ortalama sözcük uzunluğunu ve en uzun
sözcüğünü hesaplamaktır. Tek dosyalık bir betik olarak başlar; modüllere bölünür, dışa
aktarım yüzeyi tanımlanır, bir bağımlılık ekler, sürüm işaretlenir, bir çalışma alanının
parçası olur ve sonunda derleme ile çözümleme araçlarına bağlanır.
İlk hâli tek dosyadır ve hiçbir şey dışa aktarmaz:
// dosya: olcer.mjs const SOZCUK_AYIRACI = /[^\p{L}\p{N}]+/u; const CUMLE_AYIRACI = /[.?]+/; function sozcuklereBol(metin) { return metin.split(SOZCUK_AYIRACI).filter((s) => s.length > 0); } function cumlelereBol(metin) { return metin.split(CUMLE_AYIRACI).map((c) => c.trim()).filter((c) => c.length > 0); } function ortalamaUzunluk(sozcukler) { if (sozcukler.length === 0) return 0; return sozcukler.reduce((t, s) => t + s.length, 0) / sozcukler.length; } function olc(metin) { const sozcukler = sozcuklereBol(metin); return { sozcukSayisi: sozcukler.length, cumleSayisi: cumlelereBol(metin).length, ortalamaUzunluk: ortalamaUzunluk(sozcukler), }; } console.log(olc('Modul kendi kapsamini tasir. Betik genel kapsamda calisir.'));
$ node olcer.mjs
{ sozcukSayisi: 8, cumleSayisi: 2, ortalamaUzunluk: 6.125 }
Bu dosyada üç ayrı sorumluluk iç içedir: metni parçalara ayırma, sayısal ölçüm hesaplama ve
sonucu sunma. Üçü de aynı kapsamı paylaştığı için birbirinin adlarını doğrudan görür. Dosya
büyüdükçe bu görünürlük bir tasarım kararı olmaktan çıkıp bir kaza kaynağına dönüşür:
SOZCUK_AYIRACI sabiti yalnızca bölme işlevlerini ilgilendirdiği hâlde tüm dosyaya açıktır.
Bir sonraki derste bu dosya sorumluluklarına göre bölünecek ve bölünmenin dışa aktarım yüzeyine etkisi incelenecektir. Bölme işleminin asıl sorusu, hangi kodun hangi dosyaya gideceği değil, hangi adın dışarıdan görülebilir kalacağıdır.
Özet
- Betik olarak ayrıştırılan kaynağın en üst düzey bildirimleri genel kapsama yazılır; iki dosya aynı adı tanımladığında ikincisi birincisini sessizce geçersizleştirir.
- Kapanışla kurulan ad alanı kalıbı genel ad sayısını azaltır, ancak yalıtımı programcının disiplinine bırakır ve bağımlılıkları kodda bildirmez.
- Modül olarak ayrıştırılan kaynağın en üst düzey bildirimleri modül kapsamında kalır; katı
kip her zaman etkindir ve en üst düzeydeki
thistanımsızdır. - Bir modül sistemi dört güvence verir: yalıtım, açık arayüz, belirlenimci çözümleme ve tek değerlendirme.
Sonraki Adım
Modül kapsamı, adların dışarı sızmasını engeller; ama bir modülün başka bir modüle bilerek ne verdiğini henüz tanımlamadık. Sonraki ders, dilin standart modül sözdizimini ele alır: dışa aktarım biçimleri, içe aktarımın bağ kurma davranışı ve bu sözdiziminin statik yapısının neden yalnızca bir yazım tercihi olmadığı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.