ES Modules (ESM)
ES Modules'ning til darajasidagi export/import sintaksisi, uning statik tabiati (top-level cheklovi), V8'dagi uch bosqichli jarayon (Parsing, Instantiation live binding bilan, Evaluation), Tree Shaking'ga nega imkon berishi, va brauzerdagi asinxron yuklash mexanikasi.
73-dars
ES Modules (ESM)
ES Modules — JavaScript'ning rasmiy, til darajasidagi modul tizimi (ES2015/ES6'da kiritilgan). Endi IIFE va closure hiylalariga hojat yo'q — export va import kalit so'zlari native ishlaydi.
// math.js
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
export default function multiply(a, b) {
return a * b;
}
// main.js
import multiply, { add, PI } from './math.js';
console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6
console.log(PI); // 3.14159
Bu yerda ikkita eksport turi bor:
- Named export (
export function add,export const PI) — bir faylda bir nechta bo'lishi mumkin, import qilganda aynan shu nom bilan (yokiasorqali qayta nomlab) olinadi. - Default export (
export default) — bir faylda faqat bittasi, import qilganda istalgan nom bilan olinishi mumkin.
Brauzerda ishlatish uchun script tegiga type="module" qo'shiladi:
<script type="module" src="main.js"></script>
1. Nega bu Module Pattern'dan tubdan farq qiladi?
Eng muhim farq — ESM statik (static structure), Module Pattern esa dinamik (runtime) edi.
// Bu ESM'da ISHLAMAYDI (yoki kutilgandek ishlamaydi):
if (someCondition) {
import { add } from './math.js'; // ❌ Syntax error — top-level bo'lishi shart
}
import/export deklaratsiyalari faylning eng yuqori darajasida (top-level) turishi shart, shart-sharoitga bog'liq bo'lmagan holda. Nega bu cheklov qo'yilgan — bu aynan V8/engine darajasidagi afzallik uchun qilingan.
2. V8 ichida nima bo'ladi: Static Analysis va "Module Record"
Oddiy <script> (yoki CommonJS require) runtime'da ishlaydi — dvigatel kodni satrma-satr bajarib boryapti va require() chaqirilganda o'sha zahoti faylni ochib, ishga tushiradi. Bu degani — bog'liqliklar grafigi faqat kod ishlab turganda ma'lum bo'ladi.
ESM'da esa jarayon ikki alohida bosqichga bo'lingan:
1-bosqich — Parsing / Construction (Static)
V8 (aniqrog'i, uning ichidagi modul yuklovchisi) faylni hali bajarmasdan turib, faqat parse qilib, quyidagilarni aniqlaydi:
- Bu fayl qanday narsalarni export qiladi
- Bu fayl qanday boshqa fayllardan import qiladi
Shu ma'lumot asosida Module Record deb ataladigan tuzilma yaratiladi — bu har bir modul uchun uning eksport/import "xaritasi". Bularning barchasi birlashib **bog'liqlik grafigi (dependency graph)**ni hosil qiladi — hali bitta ham qator kod ishlamagan holda.
main.js
└─ import { add, PI } from './math.js'
math.js
└─ (hech narsa import qilmaydi)
2-bosqich — Instantiation (Linking)
V8 endi har bir importni tegishli exportga bog'laydi (link qiladi). Bu yerda muhim nozik nuqta bor: bog'lanish qiymat orqali emas, balki "live binding" (jonli bog'lanish) orqali amalga oshadi.
// counter.js
export let count = 0;
export function increment() {
count++;
}
// main.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1 ← o'zgardi! Nusxa emas, JONLI bog'lanish
CommonJS'da bo'lganida (module.exports), count import qilinganda uning o'sha paytdagi qiymati nusxalanardi — keyin increment() chaqirilsa ham, import qilingan count o'zgarmay qolardi. ESM'da esa count — bu xotiradagi bitta joyga ishora qiluvchi reference, shuning uchun manba o'zgarsa, import qilingan joyda ham darhol ko'rinadi.
3-bosqich — Evaluation (Execution)
Faqat shundan keyin V8 haqiqiy kodni bajaradi — har bir modul, o'z bog'liqliklaridan keyin, faqat bir marta ishga tushadi (natija keshlanadi — ikkinchi import xuddi shu modulni qayta bajarmaydi, faqat keshdagi Module Record'ga ishora qiladi).
3. Nega bu "statik" xususiyat amaliy jihatdan muhim — Tree Shaking
Aynan shu statik tahlil tufayli bundler (Webpack, Rollup, esbuild) yoki V8'ning o'zi (agar to'g'ridan-to'g'ri native ESM ishlatilsa) quyidagini bila oladi:
// utils.js
export function used() { /* ... */ }
export function unused() { /* ... */ }
// main.js
import { used } from './utils.js';
used();
Bundler kodni bajarmasdan turib, faqat statik import/export grafigini o'qib, unused funksiyasi hech qayerda import qilinmaganini bilib oladi va uni final bundle'dan butunlay olib tashlaydi. Bu — Tree Shaking (keyingi bandimiz), va u faqat ESM'ning statik tabiati tufayli mumkin. CommonJS'da require() runtime'da har qanday shart ichida, dinamik nom bilan chaqirilishi mumkinligi sababli, bunday tahlilni ishonchli qilib bo'lmaydi.
4. Module Scope va Strict Mode
Har bir ES modul avtomatik ravishda o'z scope'iga ega — Module Pattern'da buni IIFE orqali qo'lda yaratish kerak edi, endi buni til o'zi ta'minlaydi. Bundan tashqari, har bir modul avtomatik strict mode'da ishlaydi ('use strict' yozish shart emas) — bu degani, e'lon qilinmagan o'zgaruvchiga qiymat berish xato beradi, this global obyektga emas, undefinedga teng bo'ladi va hokazo.
// module.js (ESM)
console.log(this); // undefined
x = 5; // ❌ ReferenceError: x is not defined (strict mode)
5. Yuklash mexanikasi: brauzerda qanday sodir bo'ladi
Native ESM brauzerda yuklanganda:
- Brauzer HTML'ni parse qilib,
<script type="module">teglariga yetadi. - Har bir modul fayli asinxron yuklanadi (default holatda
deferkabi xatti-tutadi — DOM parse tugaguncha kutadi, lekin fayllar parallel yuklanadi). - Modul grafigi (barcha importlar zanjiri) to'liq yuklanmaguncha, hech qaysi modul bajarilmaydi.
- Grafik tayyor bo'lgach, V8 modullarni bog'liqlik tartibida (eng chuqur bog'liqlikdan boshlab, "leaf" fayllardan) bajaradi.
Bu — nega ESM CommonJS'ga qaraganda brauzer muhitida tabiiyroq: CommonJS'ning require() sinxron (blocking) ishlaydi, bu Node.js'da fayl tizimidan o'qish tez bo'lgani uchun muammo emas, lekin brauzerda tarmoq orqali sinxron yuklash imkonsiz/yomon bo'lardi.