Modullar

Modullar, kirish

Loyiha o'sib borgan sari, kodni bir nechta fayllarga โ€” modullarga โ€” ajratish zarurati tug'iladi. Modul odatda funksiya yoki funksiyalar kutubxonasini o'z ichiga oladi. Ushbu darsda modul nima ekanligini, u qanday ishlashini va uning o'ziga xos xususiyatlarini chuqur o'rganamiz.

Modul nima?

Modul โ€” bu shunchaki bitta fayl. Bitta skript โ€” bu bitta modul. Oddiy va aniq: JavaScript'da har bir fayl alohida modul hisoblanadi.

Modullar bir-birini import va export kalit so'zlari yordamida chaqira oladi va o'zaro funksionallik almashadi โ€” biri boshqasidan funksiyalarni chaqiradi:

Masalan, bizda sayHi funksiyasini eksport qiladigan fayl bo'lsa:

// ๐Ÿ“ sayHi.js
export function sayHi(user) {
  alert(`Salom, ${user}!`);
}

Boshqa faylda uni import qilib, ishlatamiz:

// ๐Ÿ“ main.js
import { sayHi } from './sayHi.js';

alert(sayHi); // function...
sayHi('Ali'); // Salom, Ali!

Bu yerda import { sayHi } from './sayHi.js' direktivasi sayHi.js fayliga nisbatan yo'l bo'yicha modulni yuklaydi va eksport qilingan sayHi funksiyasini joriy modulda foydalanish uchun mavjud qiladi.

Yo'l (path) odatda nisbiy yozuvda beriladi: './sayHi.js'. ./ โ€” joriy papkani bildiradi. Ba'zi build tizimlari kengaytmasiz ('./sayHi') yozishga ruxsat bersa-da, sof brauzer modullarida .js kengaytmasi zarur.

Brauzerda modullar: script type=module

Brauzer modullarni oddiy skriptlardan farqli ravishda yuklaydi. Modul ishlatilishi uchun HTML'da <script> tegiga maxsus atribut qo'shiladi: type="module".

<!DOCTYPE html>
<script type="module">
  import { sayHi } from './sayHi.js';

  document.body.innerHTML = sayHi('Vali');
</script>

Faqat type="module" bilan belgilangan skript ichida import ishlaydi. Oddiy <script>da import yozsangiz, xatolik yuz beradi.

Modullar faqat HTTP(s) orqali ishlaydi, local file protokoli (file://) orqali emas. Ya'ni faylni to'g'ridan-to'g'ri brauzerda ochsangiz, import/export ishlamaydi. Lokal veb-server (masalan, live server yoki VS Code kengaytmasi) ishga tushiring.
Node.js muhitida esa fayl .mjs kengaytmasiga ega bo'lsa yoki package.jsonda "type": "module" ko'rsatilgan bo'lsa, ES modullari ishlaydi. Ushbu kurs sahifasidagi interaktiv maydonchada import/export ishlamaydi โ€” shu sabab bu darslarda ular faqat statik misol sifatida keltiriladi.

Modulning o'ziga xos xususiyatlari

Modullar oddiy skriptlardan bir necha muhim jihatlari bilan farq qiladi. Ularni birma-bir ko'rib chiqamiz.

1. Har doim "use strict"

Modullar doim qat'iy rejimda (strict mode) ishlaydi. Buni alohida yozish shart emas โ€” u avtomatik yoqilgan. Masalan, e'lon qilinmagan o'zgaruvchiga qiymat berish xatolik beradi:

<script type="module">
  a = 5; // ReferenceError: a is not defined
</script>

2. Har bir modulning o'z ko'lami (scope) bor

Har bir modulning o'z yuqori darajali (top-level) ko'lami bo'ladi. Bir modulning yuqori darajali o'zgaruvchi va funksiyalari boshqa modullarda ko'rinmaydi.

// ๐Ÿ“ user.js
let user = 'John';

// ๐Ÿ“ hello.js
alert(user); // xatolik: user is not defined

Modullar tashqariga chiqarmoqchi bo'lgan narsalarni export qilishi, foydalanmoqchi bo'lganlarini esa import qilishi kerak. Ya'ni har bir modul o'z o'zgaruvchilarini yashiradi (encapsulation).

Brauzerda ham har bir <script type="module">ning alohida yuqori darajali ko'lami bo'ladi:

<script type="module">
  let user = 'John';
</script>

<script type="module">
  alert(user); // Error: user is not defined
</script>
Agar biror qiymatni haqiqatan ham global qilmoqchi bo'lsangiz (masalan, uchinchi tomon kutubxonasi uchun), uni ochiqchasiga window obyektiga tayinlashingiz kerak: window.user = user. Lekin bu kamdan-kam holatda tavsiya etiladi.

3. Modul kodi faqat bir marta baholanadi

Agar bir modul bir necha joyda import qilinsa, uning kodi faqat birinchi importda bir marta ishga tushiriladi. Natija esa barcha importer'larga uzatiladi. Bu muhim oqibatlarga ega.

Bir marta baholanish yon ta'sirlar (side-effects) uchun ahamiyatli. Masalan, modul quyidagicha xabar chiqarsin:

// ๐Ÿ“ alert.js
alert('Modul baholandi!');
// ๐Ÿ“ 1.js
import './alert.js'; // Modul baholandi!

// ๐Ÿ“ 2.js
import './alert.js'; // (hech narsa ko'rsatilmaydi)

Ikkinchi import hech qanday narsa ko'rsatmaydi, chunki modul allaqachon baholangan.

Amaliyotda bu shuni anglatadiki: modulning yuqori darajali kodi odatda ishga tayyorlash (initialization), ichki ma'lumot tuzilmalarini yaratish uchun ishlatiladi. Agar biror obyektni eksport qilsak, u barcha importer'lar uchun bir xil (yagona nusxa) bo'ladi:

// ๐Ÿ“ admin.js
export let admin = {
  name: 'John'
};
// ๐Ÿ“ 1.js
import { admin } from './admin.js';
admin.name = 'Pete';

// ๐Ÿ“ 2.js
import { admin } from './admin.js';
alert(admin.name); // Pete

// Ikkala fayl ham bir xil admin obyektiga murojaat qiladi.
// 1.js dagi o'zgarish 2.js da ham ko'rinadi.
Modul yagona nusxa (singleton) yaratadi. Bu qulay: birinchi importda modulni sozlaysiz (masalan, konfiguratsiya berasiz), keyingi importlarda esa tayyor holatini olasiz.

4. import.meta

import.meta obyekti joriy modul haqidagi ma'lumotni saqlaydi. Uning tarkibi muhitga bog'liq. Brauzerda u odatda skriptning URL manzilini o'z ichiga oladi:

<script type="module">
  alert(import.meta.url); // skript fayl URL manzili
</script>

5. "this" โ€” undefined

Modulning yuqori darajasida this qiymati undefined bo'ladi (oddiy skriptdagi window emas). Buni qat'iy rejim bilan solishtiring:

<script>
  alert(this); // window
</script>

<script type="module">
  alert(this); // undefined
</script>

Brauzerga xos xususiyatlar

Brauzer muhitida modul skriptlari yana bir necha o'ziga xoslikka ega.

Modul skriptlari kechiktirilgan (deferred)

Modul skriptlari har doim deferred โ€” huddi defer atributi qo'yilgandek ishlaydi. Bu tashqi (src bilan) va ichki (inline) modullar uchun ham amal qiladi. Bundan kelib chiqadigan xususiyatlar:

<script type="module">
  // button hujjatda pastroqda bo'lsa ham ko'rinadi,
  // chunki modul deferred โ€” u HTML tayyor bo'lgach ishlaydi
  alert(typeof button); // object
</script>

<button id="button">Button</button>

Solishtiring: oddiy (non-module) skript darhol ishga tushardi va typeof button undefined bo'lardi, chunki brauzer hali buttonni ko'rmagan bo'lardi.

Async atributi inline skriptlarda ishlaydi

Oddiy skriptlarda async atributi faqat tashqi skriptlar uchun ishlaydi. Modullarda esa u inline skriptlarda ham ishlaydi. async bilan skript boshqa skriptlar va HTML'ni kutmasdan, tayyor bo'lishi bilanoq ishga tushadi. Bu import'lar yuklangan analitika, reklama kabi mustaqil funksionallik uchun qulay.

<script async type="module">
  import { counter } from './analytics.js';
  counter.count();
</script>

Tashqi skriptlar

type="module" bilan tashqi skriptlar ikki jihatda farq qiladi:

<!-- another-site.com CORS ruxsatini berishi kerak,
     aks holda skript ishlamaydi -->
<script type="module" src="http://another-site.com/module.js"></script>

"Yalang'och" (bare) modullarga ruxsat yo'q

Brauzerda import yo'li nisbiy yoki mutlaq bo'lishi kerak. Yo'lsiz, faqat nom bilan yozilgan ("bare") modullarga ruxsat yo'q:

import { sayHi } from 'sayHi'; // Xatolik! "bare" modul
// yo'l bo'lishi kerak, masalan './sayHi.js'
Node.js va build vositalari (webpack kabi) "bare" modullarga ruxsat beradi va ularni o'zlaricha topadi (masalan, node_modulesdan). Lekin sof brauzer bunday qilmaydi.

Xulosa