IlmHamroh
JavaScript Full-stack/10-qism. JavaScript chuqur62/65-dars21 daqiqa
Mundarija (37)

JavaScript modul semantikasi: bir marta bajarilish, jonli bog'lanish va aylanma import

Qisqacha: Har ES modul o'z muhitiga ega va avtomatik qat'iy rejimda ishlaydi: yuqori darajadagi this — undefined, nomlari window ga tushmaydi. Modul necha marta import qilinmasin, bir marta bajariladi — hamma importchi bitta nusxani oladi. Import qilingan nom qiymatning nusxasi emas, eksport qilingan o'zgaruvchiga jonli bog'lanish: modul uni o'zgartirsa, importchilar yangi qiymatni ko'radi. Ikki modul bir-birini import qilsa (aylanma import), biri ikkinchisining hali tayyor bo'lmagan nomiga tegib, Cannot access ... before initialization xatosini olishi mumkin.

Bu darsda

  • Modul muhiti global muhitdan qanday farq qilishini va modulda this nimaga tengligini tushuntira olasiz.
  • Modullar qaysi tartibda va necha marta bajarilishini oldindan ayta olasiz.
  • Jonli bog'lanishni destructuring va CommonJS nusxasidan farqlaysiz.
  • Aylanma importni tanib, uni xavfsiz tuzilmaga keltira olasiz.
  • Statik tuzilma nega "keraksiz kodni tashlash" (tree-shaking) imkonini berishini bilasiz.
  • vazifalar modullarini DevTools'da kuzatib, aylanma import tajribasini o'tkazasiz.

Oldin bilishingiz kerak: ESM import, Temporal Dead Zone (TDZ), Global obyekt va globalThis, this: to'rt qoida.

1. Nega bu kerak?

O'tgan darsda uchta g'alati narsa ko'rdik va ularni "keyingi darsda" deb qoldirdik:

  • menyu.mjs ni bitta faylda ikki marta import qildik — u bir marta bajarildi.
  • Import qilingan narxlar obyektining ichini o'zgartirdik — bu o'zgarishni boshqa modullar ham ko'radi.
  • vazifalar da filtrniTanla ni holat.js ga qo'ymadik, chunki u render.js bilan "bir-birini import qiladigan" juftlik yasardi.

Uchalasi bitta savolga borib taqaladi: modul fayllari qanday yuklanadi, bajariladi va bir-biri bilan bog'lanadi? import va export — faqat sintaksis. Ularning orqasida qoidalar bor, va bu qoidalarni bilmasangiz, katta loyihada tushunarsiz xatolar chiqadi.

Hayotdan o'xshatish: choyxona oshxonasidagi qozon. Ertalab Rustam aka oshni bir marta damlaydi. Ofitsiantlar — Otabek ham, boshqalar ham — kun bo'yi o'sha qozondan suzadi. Har buyurtmaga yangi qozon damlanmaydi. Va kimdir qozonga tuz solsa, keyingi hamma likopchada sho'r osh bo'ladi. Modul — o'sha qozon.

2. Modul muhiti

2.1 O'z xonasi

Leksik scope darsida scope zanjirini ko'rdik: funksiya muhiti → tashqi muhit → … → global. Modul bu zanjirga bitta qavat qo'shadi: modul muhiti. Execution context darsida aytgandek, u global muhitdan bir qavat ichkarida turadi:

flowchart TB
  G["Global muhit<br/>console, Math, globalThis"]
  M1["menyu.mjs muhiti<br/>narxlar, narxniTop"]
  M2["kassa.mjs muhiti<br/>tushum, hisobot"]
  F["narxniTop ichi<br/>taom"]
  G --> M1
  G --> M2
  M1 --> F

Diagrammada ikki modul yonma-yon turibdi, lekin ular orasida chiziq yo'q. menyu.mjs ning nomlari kassa.mjs ga ko'rinmaydi — faqat import orqali. Ikkalasi esa global muhitni ko'radi: console, Math, JSON — hamma joyda.

Node'da tekshiramiz. tepa.mjs:

js
var narx = 35000;
function salom() {}
console.log(this);
console.log(globalThis.narx, globalThis.salom);
console.log(typeof module, typeof require);

Konsolda:

text
undefined
undefined undefined
undefined undefined

Uch qator — modul muhitining uchta belgisi:

  1. this — undefined. Modulning yuqori darajasida this hech narsaga qaramaydi. Oddiy brauzer skriptida u window, Node'ning CommonJS faylida — {} (this: to'rt qoida darsidagi jadval).
  2. var va function ham global'ga tushmaydi. Global obyekt darsida oddiy skriptning var va function lari window ga yozilishini ko'rdik. Modulda — yo'q. Scope asoslari darsida va'da qilgan "har fayl — o'z scope'i" — aynan shu.
  3. CommonJS nomlari yo'q. require, module, __dirname — Node'ning eski tizimi har faylga beradigan nomlar. ESM'da ular yo'q (CommonJS va ESM darsida).

Maslahat: Saytdagi "Ishga tushir" tugmasi kodni brauzerda oddiy skript sifatida (qat'iy rejimda) ishlatadi. Shuning uchun u yerda yuqori darajadagi this — Window, Node'dagi .mjs da esa — undefined. Modul xulqini fayl bilan, Node'da sinang.

2.2 Qat'iy rejim — avtomatik

Strict mode darsida o'rgangan hamma narsa modulda yoqilgan: e'lonsiz o'zgaruvchi — ReferenceError, faqat o'qiladigan xususiyatga yozish — TypeError, takroriy parametr — SyntaxError. "use strict" yozish shart emas — modul uni o'chira olmaydi ham.

Brauzerda bir sahifada ikkalasini solishtiring. E'tibor bering: modul skript HTML'da birinchi yozilgan, lekin uning qatori konsolda ikkinchi:

html
<p>Modul va oddiy skript</p>
<script type="module">
  var taom = "osh";
  console.log("modul:", this, window.taom);
</script>
<script>
  var choy = "ko'k choy";
  console.log("oddiy:", this === window, window.choy);
</script>

Konsolda:

text
oddiy: true ko'k choy
modul: undefined undefined

Modul defer kabi kechiktiriladi (Skriptlarni ulash), shuning uchun oddiy skript oldin ishladi. Oddiy skriptda this — window, var choy esa window.choy bo'ldi. Modulda ikkalasi ham undefined.

Tekshirib ko'ring: Modulda console.log(Math.max(1, 2)) ishlaydimi? Axir modul global'dan ajratilgan-ku.

Javob

Ishlaydi — 2. Modul global'ga yozmaydi, lekin uni o'qiydi: scope zanjiri modul muhitidan global muhitga davom etadi. Math, console, document — hammasi ko'rinadi. Ajratilgan narsa — modulning o'z nomlari: ular global'ga chiqmaydi.

3. Modul bir marta bajariladi

3.1 Uch modul, bitta sozlama

«Bahor» ilovasida sozlamalar.mjs ni ikki modul import qiladi — menyu.mjs va kassa.mjs. kassa.mjs yopilish soatini o'zgartiradi:

text
bahor-modul/
├── asosiy.mjs
├── menyu.mjs
├── kassa.mjs
└── sozlamalar.mjs
js
// sozlamalar.mjs
console.log("sozlamalar.mjs bajarildi");
export const sozlamalar = { ochilish: 7, yopilish: 23 };
js
// menyu.mjs
import { sozlamalar } from "./sozlamalar.mjs";
console.log("menyu.mjs bajarildi");
export const menyuSozlama = sozlamalar;
js
// kassa.mjs
import { sozlamalar } from "./sozlamalar.mjs";
console.log("kassa.mjs bajarildi");
sozlamalar.yopilish = 22;
export const kassaSozlama = sozlamalar;
js
// asosiy.mjs
import { menyuSozlama } from "./menyu.mjs";
import { kassaSozlama } from "./kassa.mjs";
console.log("asosiy.mjs bajarildi");
console.log(menyuSozlama === kassaSozlama);
console.log(menyuSozlama.yopilish);

node asosiy.mjs:

text
sozlamalar.mjs bajarildi
menyu.mjs bajarildi
kassa.mjs bajarildi
asosiy.mjs bajarildi
true
22

Uch xulosa:

  • sozlamalar.mjs bir marta bajarildi, garchi uni ikki modul import qilgan bo'lsa ham.
  • Ikki importchi bitta obyektni oldi — true. Nusxa yo'q.
  • kassa.mjs ning o'zgarishini menyu ham ko'rdi — 22. «Nega bu kerak?» dagi qozonga solingan tuz.

3.2 Modul xaritasi

Dvigatel buni qanday biladi? U modul xaritasini (module map) yuritadi: "shu manzildagi modul allaqachon yuklanganmi?" Kalit — modulning to'liq manzili (URL). Birinchi import modulni yuklab, bajarib, natijasini xaritaga yozadi. Keyingi importlar to'g'ridan-to'g'ri xaritadan oladi.

Kalit aynan URL ekanini tekshirish oson: manzilga so'rov qismi (?...) qo'shsak, URL boshqacha bo'ladi:

js
import { sozlamalar as a } from "./sozlamalar.mjs";
import { sozlamalar as b } from "./sozlamalar.mjs?nusxa";
console.log(a === b);
text
sozlamalar.mjs bajarildi
sozlamalar.mjs bajarildi
false

Bitta fayl — ikki xil URL — ikki modul, ikki bajarilish, ikki obyekt. Amalda bunday qilinmaydi. Lekin bu brauzer keshi bilan ishlashda uchraydi (menyu.js?v=2) va uni bilish "nega modulim ikki marta ishladi?" degan savolga javob beradi.

3.3 Singleton — bepul

JS'da dizayn naqshlari darsida "bitta nusxa kerak bo'lsa — bitta obyekt yarating, modulda u yanada aniq" degan edik. Mana sababi: moduldan eksport qilingan obyekt butun dastur uchun yagona. static #yagona va olish() kerak emas.

Lekin bu — ikki tomonlama qilich. Yagona o'zgaruvchan obyekt — yashirin global holat: istalgan modul uni o'zgartiradi, qolganlari esa sababini bilmaydi. Yuqoridagi kassa.mjs — xavfli kod namunasi. Ikki yo'l bilan himoyalaning:

  • Sozlamani muzlating — export const sozlamalar = Object.freeze({...}). Qat'iy rejimda kassa.mjs darhol TypeError oladi.
  • O'zgarishni funksiya orqali qiling — export function yopilishniOzgartir(soat). O'zgarish bitta joyda, kuzatish oson.

Tekshirib ko'ring: sozlamalar.mjs da Object.freeze bo'lsa, node asosiy.mjs nima beradi?

Javob

sozlamalar.mjs bajarildi, menyu.mjs bajarildi, kassa.mjs bajarildi — va keyin TypeError: Cannot assign to read only property 'yopilish' of object '#<Object>'. asosiy.mjs ning qatorlari chiqmaydi: dastur kassa.mjs da to'xtadi. Jim o'zgarish o'rniga baland xato — qat'iy rejim va muzlatish birga ishladi.

4. Jonli bog'lanish

4.1 Hisoblagich tajribasi

hisoblagich.mjs buyurtmalarni sanaydi va sonni let bilan eksport qiladi:

js
// hisoblagich.mjs
export let buyurtmalar = 0;

export function buyurtmaQosh() {
  buyurtmalar++;
}
js
// asosiy.mjs
import { buyurtmalar, buyurtmaQosh } from "./hisoblagich.mjs";

console.log(buyurtmalar);
buyurtmaQosh();
buyurtmaQosh();
console.log(buyurtmalar);
text
0
2

asosiy.mjs ning buyurtmalar i o'zi o'zgardi! Agar import nusxa olganda, ikkinchi qator ham 0 bo'lardi. Taqqoslash uchun — destructuring, xuddi shu holat oddiy obyekt bilan:

js
const hisoblagich = {
  buyurtmalar: 0,
  buyurtmaQosh() {
    this.buyurtmalar++;
  },
};

const { buyurtmalar } = hisoblagich;
hisoblagich.buyurtmaQosh();
hisoblagich.buyurtmaQosh();
console.log(buyurtmalar, hisoblagich.buyurtmalar); // 0 2

Destructuring 0 ni nusxaladi: buyurtmalar — yangi, mustaqil o'zgaruvchi. Import esa nusxa olmaydi.

4.2 Bog'lanish nima?

Jonli bog'lanish (live binding) — import qilingan nom eksport qilingan o'zgaruvchining o'ziga qaraydi, uning qiymatining nusxasiga emas. Hayotdan o'xshatish: choyxona devoridagi "bugungi osh narxi" tablosi. Har mijoz uning rasmini olmaydi — hamma bitta tabloga qaraydi. Jasur aka tablodagi raqamni almashtirsa, hamma yangi narxni ko'radi.

flowchart LR
  subgraph H["hisoblagich.mjs"]
    X["buyurtmalar — 2"]
  end
  A["asosiy.mjs:<br/>buyurtmalar"] -- "jonli" --> X
  K["kassa.mjs:<br/>buyurtmalar"] -- "jonli" --> X

Ikki importchi — bitta "xona". Strelkalar qiymatga emas, xonaga qaraydi.

4.3 Kim o'zgartira oladi

Bog'lanish bir tomonlama: qiymatni faqat eksport qilgan modul o'zgartiradi. Importchi uchun nom faqat o'qish uchun — buyurtmalar = 10 yozsa, ESM import darsidagi TypeError: Assignment to constant variable. chiqadi. O'zgartirish kerak bo'lsa — modul eksport qilgan funksiya orqali (buyurtmaQosh).

Bu nega yaxshi? Holat bitta joyda o'zgaradi, hamma esa doim eng yangi qiymatni ko'radi. Nega xavfli? export let dagi qiymat "o'z-o'zidan" o'zgarib turadi — kodni o'qigan odam buni kutmasligi mumkin. Shuning uchun odat: o'zgaruvchan let ni kamdan-kam eksport qiling. Ko'pincha const + funksiya (buyurtmalarSoni()) tushunarliroq.

Maslahat: Node'ning eski tizimi — CommonJS — boshqacha: require eksport obyektining o'sha paytdagi nusxa qiymatlarini oladi, va destructuring qilsangiz, yuqoridagi 0 chiqadi. Bu farqni CommonJS va ESM darsida ko'ramiz.

Tekshirib ko'ring: import * as h from "./hisoblagich.mjs" qilib, h.buyurtmaQosh() dan keyin h.buyurtmalar ni chiqarsak — nima chiqadi?

Javob

1. Modul obyekti ham jonli: uning xususiyatlari eksport qilingan o'zgaruvchilarga bog'langan. Shuning uchun u faqat o'qiladigan — h.buyurtmalar = 5 yozib bo'lmaydi.

5. Modullar qaysi tartibda bajariladi

5.1 Avval bolalar, keyin ota

Brauzer yoki Node modul bilan uch bosqichda ishlaydi:

  1. Yuklash va o'qish. Kirish faylidan boshlab, har import manzili bo'yicha fayllarni topadi va yuklaydi — butun modul grafigi (module graph) yig'iladi. Kod hali bajarilmaydi.
  2. Bog'lash. Har import nomini eksport "xonasi"ga ulaydi. Noto'g'ri nom shu bosqichda SyntaxError beradi.
  3. Bajarish. Modullar kodini ishga tushiradi: har modul o'z importlari bajarilgandan keyin, va har biri bir marta.

Uchinchi bosqichdagi tartibni ESM import darsidagi «Tartibni oldindan ayting» mashqida ko'rgan edingiz: chuqurlikka qarab, avval eng pastdagi modul. vazifalar modullariga har birining boshiga console.log qo'yib tekshirdik — brauzerda tartib shunday:

flowchart TD
  A["asosiy.js — 6"] --> R["render.js — 5"]
  A --> H["holat.js — 4"]
  A --> P["paket.js — 3"]
  R --> H
  R --> V["vazifa.js — 1"]
  H --> RO["royxat.js — 2"]
  P --> V
  RO --> V

Raqamlar — bajarilish tartibi (diagrammada asosiy.js dan vazifa.js va royxat.js ga to'g'ridan-to'g'ri strelkalar soddalik uchun tushirilgan). asosiy.js importlari tartibi: vazifa, royxat, paket, holat, render. Dvigatel ularni birma-bir ochadi:

  1. vazifa.js hech narsani import qilmaydi — birinchi bajariladi.
  2. royxat.js — uning vazifa.js i tayyor.
  3. paket.js — xuddi shunday.
  4. holat.js — royxat.js tayyor.
  5. render.js — vazifa.js va holat.js tayyor.
  6. Eng oxirida kirish nuqtasi — asosiy.js.

Bu tartib kafolat beradi: modul kodi ishlaganda, u import qilgan hamma narsa tayyor. render.js ning birinchi qatori ishlaganda holat allaqachon yaratilgan.

Tekshirib ko'ring: asosiy.js ning import qatorlarini almashtirib, render.js ni birinchi qo'ysak, qaysi modul birinchi bajariladi?

Javob

Yana vazifa.js. render.js ning birinchi importi — vazifa.js (u hech narsani import qilmaydi), shuning uchun u birinchi. Keyin render.js ning ikkinchi importi holat.js → u royxat.js ni talab qiladi. Tartib: vazifa, royxat, holat, render, keyin paket, oxirida asosiy (Node 24 da tekshirdik). Qoida o'zgarmaydi: eng chuqurdagi, hech narsaga bog'liq bo'lmagan modul birinchi.

5.2 Statik tuzilma

Birinchi bosqichda dvigatel kodni bajarmaydi, faqat o'qiydi. Shuning uchun import va eksport statik bo'lishi shart: yuqori darajada, oddiy satr manzil bilan (ESM import darsidagi uchta qoida). Bundan uchta foyda keladi:

  • Xatolar oldindan. Noto'g'ri nom yoki yo'q fayl — kod ishga tushmasidan oldin.
  • Parallel yuklash. Brauzer grafikdagi fayllarni bir vaqtda so'raydi (Brauzerda ESM darsida o'lchaymiz).
  • Asboblar kodni tushunadi. VS Code nom qayerdan kelganini biladi, bundler esa keraksiz kodni tashlab yuboradi — keyingi bo'lim.

6. Aylanma import

6.1 Ikki modul bir-birini import qiladi

Aylanma import (circular import) — A modul B ni import qiladi, B esa A ni. «Bahor» misoli: menyu.mjs buyurtma berishda kassaga chek yozdiradi, kassa.mjs esa chekda narxni menyudan oladi:

js
// menyu.mjs
import { chekniYoz } from "./kassa.mjs";

console.log("menyu.mjs bajarilmoqda");
export const narxlar = { osh: 35000, choy: 5000 };

export function buyurtmaBer(taom) {
  chekniYoz(taom);
}
js
// kassa.mjs
import { narxlar } from "./menyu.mjs";

console.log("kassa.mjs bajarilmoqda");

export function chekniYoz(taom) {
  console.log(`Chek: ${taom} — ${narxlar[taom]} so'm`);
}
js
// asosiy.mjs
import { buyurtmaBer } from "./menyu.mjs";

buyurtmaBer("osh");
text
kassa.mjs bajarilmoqda
menyu.mjs bajarilmoqda
Chek: osh — 35000 so'm

Ishladi. Qanday? «Avval bolalar, keyin ota» qoidasi bo'yicha, qadamma-qadam:

  1. asosiy → menyu ni so'raydi; menyu → kassa ni; kassa → yana menyu ni.
  2. menyu allaqachon "jarayonda" — dvigatel aylanani shu yerda uzadi va kassa.mjs ni birinchi bajaradi. Bu paytda narxlar hali yaratilmagan.
  3. Lekin kassa.mjs narxlar ni yuqori darajada ishlatmaydi — faqat chekniYoz ichida.
  4. Keyin menyu.mjs bajariladi, narxlar tayyor bo'ladi.
  5. buyurtmaBer("osh") chaqirilganda jonli bog'lanish tayyor narxlar ni ko'rsatadi.

6.2 Qachon buziladi

Endi kassa.mjs ga bitta qator qo'shamiz — choy narxini yuqori darajada, bir marta o'qib qo'yish:

js
// kassa.mjs
import { narxlar } from "./menyu.mjs";

console.log("kassa.mjs bajarilmoqda");
const choyNarxi = narxlar.choy;
text
kassa.mjs bajarilmoqda
ReferenceError: Cannot access 'narxlar' before initialization

Tarjimasi: "narxlar ga u ishga tushirilishidan oldin murojaat qilib bo'lmaydi". Bu — TDZ darsidagi xabarning o'zi. O'sha darsda "modul fayllari bir-birini import qilganda ham shu xabar chiqadi" degan edik. Mana: narxlar bog'langan, lekin menyu.mjs hali const narxlar = ... qatoriga yetmagan. Nom bor, qiymat yo'q — TDZ.

Qadamma-qadam:

sequenceDiagram
  participant A as asosiy
  participant M as menyu
  participant K as kassa
  A->>M: import
  M->>K: import
  K-->>M: menyu jarayonda — kutmaydi
  Note over K: kassa bajariladi
  Note over K: narxlar.choy — TDZ xato
  Note over M: menyu hali boshlanmagan

Eng xavflisi: xato qaysi fayl kirish nuqtasi bo'lishiga bog'liq. asosiy.mjs kassa.mjs ni birinchi import qilsa, tartib teskari bo'ladi va xato boshqa joyda chiqadi yoki umuman chiqmaydi. Kod bir sahifada ishlab, boshqasida yiqiladi.

6.3 Aylanadan qochish

Qoida: modul grafigi bir tomonga yursin. Aylanma import ko'rinsa, uch yo'l bor:

  1. Umumiy qismni uchinchi modulga chiqaring. narxlar ni narxlar.mjs ga olib chiqsak, menyu ham, kassa ham undan oladi — aylana yo'q.
  2. Bog'liqlikni teskari qiling. Kimdir kimgadir funksiya uzatsin (callback yoki kuzatuvchi), import qilmasin.
  3. Yuqori darajada ishlatmang. Aylana qolsa ham, boshqa modul nomlarini faqat funksiya ichida ishlating. Bu — eng zaif himoya: bitta beparvo qator hammasini buzadi.

Tekshirib ko'ring: a.mjs b.mjs dan function b() {} ni import qiladi va uni yuqori darajada chaqiradi; b.mjs esa a.mjs ni import qiladi. a.mjs — kirish nuqtasi. Xato bormi?

Javob

Yo'q. Tartib: a → b (u a ni import qiladi, lekin a jarayonda — o'tkaziladi) → b bajariladi → a bajariladi va b() ni chaqiradi. b modul allaqachon bajarilgan, demak funksiya tayyor. Bundan tashqari, function e'loni modul bajarilishidan oldin tayyorlanadi (Hoisting). Xato const, let yoki class ga ularning qatoriga yetmasdan tegilganda chiqadi.

7. Keraksiz kodni tashlash

7.1 Tree-shaking

Statik tuzilmaning eng katta amaliy foydasi. yordam.js da ikki funksiya, asosiy.js esa faqat bittasini import qiladi:

js
// yordam.js
export function somYoz(son) {
  return `${son} so'm`;
}

export function vaqtYoz(soat) {
  return `${soat}:00`;
}
js
// asosiy.js
import { somYoz } from "./yordam.js";

console.log(somYoz(35000));

Bundler — ko'p modulni brauzer uchun bitta (yoki bir nechta) faylga yig'adigan asbob. Mashhurlaridan biri — esbuild (Vite uning ustiga qurilgan; Vite'ni 16-qismda o'rganamiz, hozir bilish shart emas). Biz shu ikki faylni esbuild 0.25 bilan yig'dik (npx esbuild asosiy.js --bundle --format=esm):

js
// yordam.js
function somYoz(son) {
  return `${son} so'm`;
}

// asosiy.js
console.log(somYoz(35e3));

vaqtYoz natijada yo'q. Bundler grafikni o'qib, hech kim import qilmagan eksportni tashlab yubordi. Bu — tree-shaking ("daraxtni silkitish"): quruq barglar to'kiladi, kerakligi qoladi. Natija fayl kichrayadi — telefonda sekin internetli Aziz uchun bu tezroq sahifa. (35e3 — 35000 ning qisqa yozuvi: bundler sonni ham ixchamladi.)

7.2 CommonJS bilan — tashlanmaydi

Xuddi shu ikki funksiyani CommonJS'da (exports.somYoz = ..., require(...)) yozib, xuddi shunday yig'sak, natijada vaqtYoz qoladi. require() — oddiy funksiya chaqiruvi: u kod ishlayotganda, istalgan joyda, istalgan manzil bilan chaqirilishi mumkin. Bundler kod ishlamasdan "nima ishlatiladi" degan savolga ishonchli javob bera olmaydi va hamma narsani saqlab qoladi. ESM'ning statik qoidalari — aynan shu tahlil uchun.

7.3 Yon ta'sir to'sig'i

Tree-shaking faqat toza modullarda to'liq ishlaydi. Agar modul yuqori darajada yon ta'sir qilsa — konsolga yozsa, window ga nimadir qo'shsa — bundler uni tashlay olmaydi: u ishlatilmasa ham, uning yon ta'siri kerak bo'lishi mumkin. Shuning uchun yaxshi modul yuqori darajada faqat e'lon qiladi, ishni esa funksiyalar bajaradi. Pure funksiya g'oyasi modul darajasida.

Tekshirib ko'ring: yordam.js ning yuqori darajasiga console.log("yordam yuklandi"); qo'shilsa, asosiy.js esa import qilgan somYoz ni chaqirmasa — bundler bu qatorni tashlab yuboradimi?

Javob

Yo'q. Ishlatilmagan funksiyalar (somYoz, vaqtYoz) tashlanadi, lekin yuqori darajadagi console.log — yon ta'sir: u bajarilishi kerak bo'lishi mumkin. esbuild 0.25 bilan sinadik: natijada faqat console.log("yordam yuklandi") va asosiy.js ning o'z qatori qoldi.

8. Ko'p uchraydigan xatolar

8.1 Aylanma importda TDZ

ReferenceError: Cannot access '...' before initialization — ikki modul bir-birini import qiladi va biri ikkinchisining const/let/class iga yuqori darajada tegdi. Tuzatish: «Aylanadan qochish» bo'limi. Chrome DevTools'da xato qatoridagi fayl nomi — aylananing bir uchi; o'sha fayl nimani import qilishini tekshiring.

8.2 Umumiy obyektni o'zgartirish

Bir modul import qilgan obyektni o'zgartiradi, boshqasida esa "o'z-o'zidan" narsa o'zgaradi. Xato xabari yo'q — shuning uchun xavfli. Tuzatish: umumiy ma'lumotni muzlating, o'zgarishni eksport qilingan funksiya orqali qiling.

8.3 Har importda yangi nusxa kutish

"Ikki joyda import qildim — ikkita hisoblagich bo'ladi" — yo'q, bitta. Har importchiga alohida holat kerak bo'lsa, modul obyekt emas, fabrika (yoki klass) eksport qilsin: export function hisoblagichYarat() (Closure amaliyotda).

8.4 Console'dan modul nomini qidirish

DevTools Console'da holat deb yozasiz — Uncaught ReferenceError: holat is not defined. Bu xato emas: Console global muhitda ishlaydi, modul nomlari esa u yerda yo'q. Kuzatish usuli — «Vazifalar qadami» da.

9. Mashqlar

1-mashq (oson): Necha marta?

log.mjs ning birinchi qatori: console.log("log.mjs yuklandi");. Uni a.mjs, b.mjs va asosiy.mjs — uchalasi import qiladi, asosiy.mjs esa a.mjs va b.mjs ni ham import qiladi. node asosiy.mjs da "log.mjs yuklandi" necha marta chiqadi? b.mjs dagi import "./log.mjs?b" bo'lsa-chi?

Yechim

Birinchi holatda — bir marta: uch import, bitta URL, bitta modul. Ikkinchi holatda — ikki marta: "./log.mjs" va "./log.mjs?b" — ikki xil URL, modul xaritasida ikki yozuv. «Modul xaritasi» bo'limidagi ?nusxa tajribasi.

2-mashq (o'rta): Jonli yoki nusxa?

Natijani oldindan ayting, keyin Node'da tekshiring.

js
// navbat.mjs
export let navbatda = ["Ali"];
export let soni = 1;

export function qosh(ism) {
  navbatda.push(ism);
  soni++;
}

export function tozala() {
  navbatda = [];
  soni = 0;
}
js
// asosiy.mjs
import { navbatda, soni, qosh, tozala } from "./navbat.mjs";

const nusxa = navbatda;
qosh("Malika");
console.log(soni, navbatda, nusxa);
tozala();
console.log(soni, navbatda, nusxa);
Yechim
text
2 [ 'Ali', 'Malika' ] [ 'Ali', 'Malika' ]
0 [] [ 'Ali', 'Malika' ]
  • qosh dan keyin: soni jonli — 2. navbatda ham jonli. nusxa esa o'sha massivga qaraydi — push bitta massivni o'zgartirdi, ikkalasida Malika bor.
  • tozala dan keyin: modul navbatda ga yangi massiv berdi. Import qilingan navbatda jonli — u yangi bo'sh massivni ko'rsatdi. nusxa esa oddiy const: u eski massivni ushlab qoldi.

Jonli bog'lanish — o'zgaruvchiga, havola esa — obyektga. Ikkalasi birga ishlaydi, lekin har xil narsa (Havola semantikasi).

3-mashq (qiyin): Aylanani uzing

«Qachon buziladi» bo'limidagi menyu.mjs ↔ kassa.mjs juftligini qayta tuzing: kassa.mjs yuqori darajada choyNarxi ni o'qisin va hech qanday xato bo'lmasin. Ikkala fayl ham o'z vazifasini saqlasin. Ishora: «Aylanadan qochish» bo'limining birinchi yo'li.

Yechim

Narxlar alohida modulga chiqadi — hech kimni import qilmaydigan "pastki qavat":

js
// narxlar.mjs
export const narxlar = { osh: 35000, choy: 5000 };
js
// kassa.mjs
import { narxlar } from "./narxlar.mjs";

const choyNarxi = narxlar.choy;
console.log(`Choy narxi: ${choyNarxi} so'm`);

export function chekniYoz(taom) {
  console.log(`Chek: ${taom} — ${narxlar[taom]} so'm`);
}
js
// menyu.mjs
import { chekniYoz } from "./kassa.mjs";

export function buyurtmaBer(taom) {
  chekniYoz(taom);
}

asosiy.mjs o'zgarmaydi. node asosiy.mjs:

text
Choy narxi: 5000 so'm
Chek: osh — 35000 so'm

Grafik endi bir tomonga: asosiy → menyu → kassa → narxlar. narxlar.mjs eng birinchi bajariladi, shuning uchun kassa.mjs ning yuqori darajasi uni tayyor holda ko'radi. Qaysi fayl kirish nuqtasi bo'lishidan qat'i nazar.

4-mashq: Vazifalar qadami — modullarni kuzatish va aylana tajribasi

refactor/modullar branch'ida davom etamiz. Endi ilova oltita modulda. Bu qadamda kodni deyarli o'zgartirmaymiz — modullar qanday ishlashini o'z ilovamizda ko'ramiz.

  1. Console'dan holatni ko'rish. DevTools Console'da holat yozing. Nima bo'ladi? Holatni qanday ko'rish mumkin?
  2. Aylana tajribasi. filtrniTanla ni vaqtincha asosiy.js dan holat.js ga ko'chiring (unga render kerak bo'ladi). Ilova ishlaydimi? So'ng render.js ning boshiga, boshMatnlar dan oldin, const boshFiltr = holat.filtr; qatorini qo'shib ko'ring.
  3. README. README.md ga "Modullar" bo'limini qo'shing: har fayl nima uchun javob beradi va bog'liqlik qoidasi.
Yechim

1. Console. Uncaught ReferenceError: holat is not defined. Bu to'g'ri: Console global muhitda ishlaydi, holat esa holat.js modulining nomi. Ikki usul bilan kuzating:

  • To'xtash nuqtasi. DevTools → Sources → assets/js/render.js → render funksiyasining birinchi qatoriga breakpoint qo'ying (DevTools bilan debug qilish). Biror vazifani belgilang. Kod to'xtaganda Console shu modul muhitida ishlaydi: holat, korinadiganlar() — hammasi ko'rinadi. Scope panelida esa "Module" bo'limi bor — modul muhiti.
  • Vaqtinchalik oyna. asosiy.js oxiriga globalThis.holat = holat; yozsangiz, Console'dan ko'rinadi. Bu — faqat xato qidirish paytida, commit'ga tushmasin. O'chirishni unutsangiz, Global obyekt darsidagi muammoni o'zingiz qaytarasiz.

2. Aylana. holat.js ga:

js
import { render } from "./render.js";

// ...

export function filtrniTanla(filtr) {
  holat.filtr = filtr;
  holat.tahrirId = null;
  render();
}

asosiy.js da filtrniTanla funksiyasi o'chiriladi, holat.js dan importga qo'shiladi. Endi holat.js → render.js → holat.js — aylana. Ilova ishlaydi — biz 22 holatli ssenariyni qayta o'tkazdik, natija asl bilan bir xil. Sababi «Ikki modul bir-birini import qiladi» bo'limidagidek: render.js holat ni faqat funksiyalar ichida ishlatadi.

Endi render.js ga const boshFiltr = holat.filtr; qatorini qo'shing. Sahifa ochiladi — ro'yxat bo'sh, konsolda:

text
Uncaught ReferenceError: Cannot access 'holat' before initialization

Bitta "zararsiz" qator butun ilovani to'xtatdi. Tartib: asosiy.js importlarini birma-bir ochadi — vazifa.js, royxat.js, paket.js, so'ng holat.js. holat.js endi render.js ni import qiladi. render.js ning holat.js importi aylanani yopadi: holat.js boshlangan, lekin hali bajarilmagan. Shuning uchun render.js birinchi bajariladi va holat ga tegadi — TDZ.

Ikkala o'zgarishni ham bekor qiling: git restore assets/js. Xulosa: filtrniTanla asosiy.js da turgani — tasodif emas. Strelkalar faqat pastga yursin: asosiy → render → holat → royxat → vazifa; paket → vazifa.

3. README. README.md ga:

markdown
## Modullar

Kirish nuqtasi — `assets/js/asosiy.js` (`index.html` da
`<script type="module">`). Bog'liqlik faqat pastga yuradi:

```mermaid
flowchart TD
  asosiy.js --> render.js
  asosiy.js --> holat.js
  asosiy.js --> paket.js
  asosiy.js --> royxat.js
  asosiy.js --> vazifa.js
  render.js --> holat.js
  render.js --> vazifa.js
  holat.js --> royxat.js
  paket.js --> vazifa.js
  royxat.js --> vazifa.js
```

- `vazifa.js` — `Vazifa`, `VazifaXatosi`: bitta vazifa.
- `royxat.js` — `VazifalarRoyxati`: to'plam, hisob, teglar, obuna.
- `paket.js` — eksport va import: `{ versiya: 1, vazifalar }`.
- `holat.js` — `holat`, so'rovlar, ro'yxatni o'zgartiruvchilar.
- `render.js` — DOM: chizish, fokus, e'lon.
- `asosiy.js` — tinglovchilar va ishga tushirish.

Qoida: pastdagi modul yuqoridagini import qilmaydi
(aylanma import — TDZ xatosi).

GitHub mermaid blokini README'da diagramma qilib chizadi — repo sahifasida modul grafigi ko'rinadi.

bash
git add README.md
git commit -m "Docs: README ga modullar xaritasini qo'sh"

10. Real ishda

  • Singleton xizmatlar. Real ilovalarda API klient, sozlamalar, log yozuvchi ko'pincha bitta moduldan eksport qilingan yagona obyekt. Testlarda esa bu muammo: bir test o'zgartirgan holat keyingisiga o'tadi. Shuning uchun ko'p jamoalar "fabrika eksport qil" qoidasiga amal qiladi (testlar — 12-qismda).
  • Aylanma import — katta loyihalarning dardi. "Ilova faqat ba'zi sahifalarda ochilmayapti" degan xatoning sababi ko'pincha aylana. ESLint'ning import/no-cycle qoidasi va madge kabi asboblar grafikdagi aylanalarni topadi.
  • Bundle hajmi. Frontend jamoalari tree-shaking natijasini muntazam o'lchaydi: kutubxonadan bitta funksiya import qilib, butun kutubxonani saytga qo'shib qo'yish — tez-tez uchraydigan xato. Vite va esbuild — 16-qismda.
  • Intervyu. "ES module bir necha marta import qilinsa nima bo'ladi?", "Live binding nima?", "Circular dependency qanday hal qilinadi?", "Tree-shaking nima uchun ESM talab qiladi?" — middle darajadagi tez-tez savollar.

Xulosa

  • Modul — o'z muhiti: nomlari global'ga tushmaydi, yuqori darajadagi this — undefined, qat'iy rejim — avtomatik.
  • Modul URL bo'yicha bir marta bajariladi; hamma importchi bitta natijani oladi — yagona nusxa bepul, lekin o'zgaruvchan umumiy holat xavfli.
  • Import — jonli bog'lanish: eksport qilgan modul o'zgaruvchini o'zgartirsa, hamma yangi qiymatni ko'radi; o'zgartirishni faqat egasi qiladi.
  • Bajarilish tartibi: avval importlar (chuqurlikka), keyin modulning o'zi.
  • Aylanma importda yuqori darajadagi murojaat TDZ xatosi beradi; grafik bir tomonga yursin.
  • Statik import/export bundler'ga keraksiz eksportlarni tashlash (tree-shaking) imkonini beradi.

Keyingi dars: Brauzerda ESM va import maps — modullar nega faqat server orqali ishlaydi (CORS), yalang nomlar import xaritasi bilan qanday ulanadi va modulepreload yuklashni qanchalik tezlashtiradi.

Manbalar

  • MDN: "JavaScript modules" — "Other differences between modules and classic scripts", "Cyclic import" — developer.mozilla.org
  • ECMAScript 2025 Language Specification: "Cyclic Module Records", "InnerModuleEvaluation" — tc39.es/ecma262
  • esbuild hujjatlari: "Tree shaking" — esbuild.github.io
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript modul semantikasi: bir marta bajarilish, jonli bog'lanish va aylanma import — IlmHamroh