Seriya: JavaScript Basics Dars 74

CommonJS (CJS)

CommonJS'ning require/module.exports mexanizmi, Node.js'dagi Resolve-Load-Wrap-Execute-Cache besh bosqichi, module.exports vs exports chalkashligi, va CommonJS'ning qiymat-nusxa bog'lanishi ESM'ning live binding'idan nega farq qilishi.

74-dars

CommonJS (CJS)

CommonJS 2009-yilda, Node.js yaratilishi bilan bir vaqtda paydo bo'ldi. O'sha paytda ES Modules umuman mavjud emas edi (ESM faqat 2015-yilda standartlashdi). Node.js serverda ishlaydigan JS uchun fayl tizimidan modul yuklash mexanizmiga muhtoj edi, va CommonJS spetsifikatsiyasi shu ehtiyojni qondirish uchun ishlab chiqilgan.

1. Oddiy sintaksis

// math.js
function add(a, b) {
  return a + b;
}

const PI = 3.14159;

module.exports = { add, PI };
// main.js
const { add, PI } = require('./math.js');

console.log(add(2, 3)); // 5

Bu yerda require va module.exports — bular til sintaksisi emas, oddiy JavaScript funksiya va obyekt. import/export kabi maxsus kalit so'z emas — shuning uchun ularni shart ichida, funksiya ichida, istalgan joyda chaqirish mumkin (bu — ESM'ning "faqat top-level" cheklovidan tubdan farqli, dinamik xatti-harakat):

function loadModule(condition) {
  if (condition) {
    const mod = require('./a.js'); // ✅ butunlay to'g'ri, hech qanday xato yo'q
    return mod;
  }
  return require('./b.js');
}

2. require() ichida nima bo'ladi — Node.js darajasida

require('./math.js') chaqirilganda, Node.js orqasida quyidagi qadamlarni bajaradi:

1. Resolve — requirega berilgan satr ('./math.js') haqiqiy fayl yo'liga aylantiriladi. Agar kengaytma yozilmagan bo'lsa (require('./math')), Node avtomatik .js, .json, .node kengaytmalarini, keyin index.jsni sinab ko'radi.

2. Load — fayl diskdan sinxron o'qiladi (fs.readFileSync kabi). Aynan shu sinxronlik — CommonJS'ning brauzerga mos kelmasligining asosiy sababi: tarmoq orqali fayl yuklashda sinxron kutish saytni "muzlatib" qo'yardi, lekin serverda diskdan o'qish tez bo'lgani uchun bu muammo emas.

3. Wrap — Node har bir CommonJS faylni avtomatik ravishda funksiya ichiga o'raydi (bu — CommonJS'da modul scope qanday ishlashining siri):

// Node "orqada" sizning kodingizni shunga aylantiradi:
function (exports, require, module, __filename, __dirname) {
  // sizning math.js kodingiz shu yerda
  function add(a, b) { return a + b; }
  const PI = 3.14159;
  module.exports = { add, PI };
}

Shuning uchun har bir CommonJS faylida require, module, exports, __filename, __dirname — bular hech qayerdan import qilinmagan holda avtomatik mavjud bo'ladi. Bu — Module Pattern'da IIFE orqali qo'lda qilingan narsaning Node.js tomonidan avtomatlashtirilgan versiyasi, xuddi shu g'oya: funksiya = scope izolyatsiyasi.

4. Execute — o'ralgan funksiya darhol chaqiriladi, kod yuqoridan pastga qarab satrma-satr bajariladi.

5. Cache — natija (module.exportsning yakuniy qiymati) require.cache obyektiga fayl yo'li bo'yicha saqlanadi. Xuddi shu faylga ikkinchi marta require() chaqirilsa, kod qayta bajarilmaydi — to'g'ridan-to'g'ri keshdan qaytariladi.

// birinchi chaqiruv - fayl to'liq bajariladi, natija keshlanadi
const a1 = require('./math.js');
// ikkinchi chaqiruv - kesh ishlatiladi, kod qayta ishlamaydi
const a2 = require('./math.js');

console.log(a1 === a2); // true - aynan bir xil obyekt

3. module.exports vs exports — ko'p uchraydigan chalkashlik

exports — bu shunchaki module.exportsga ishora qiluvchi o'zgaruvchi (Wrap bosqichidagi parametrlardan biri, eslaysizmi):

// Node ichida (soddalashtirilgan):
module.exports = {};
exports = module.exports; // ikkalasi bir xil obyektga ishora qiladi

Shuning uchun quyidagi ishlaydi:

exports.add = function (a, b) { return a + b; }; // ✅ ishlaydi

Lekin bu ishlamaydi:

exports = { add: function (a, b) { return a + b; } }; // ❌ ishlamaydi!

Nega? Chunki exports = {...} — exports o'zgaruvchisini yangi obyektga qayta bog'laydi, lekin module.exports haligacha eski, bo'sh obyektga ishora qilib turibdi. Node faqat module.exportsning oxirgi qiymatini qaytaradi, exports o'zgaruvchisining o'zini emas. Bu — reference va reassignment orasidagi farqning klassik namunasi (shallow copy mavzusiga o'xshash mantiq: exportsni qayta yozish uni module.exportsdan uzib qo'yadi, lekin uning ichidagi property'larni o'zgartirish ikkalasiga ham ta'sir qiladi).


4. CommonJS'da "live binding" yo'q — qiymat nusxalanadi

Oldingi mavzuda ko'rganimizdek, ESM'da "live binding" bor edi. CommonJS'da esa module.exports orqali qaytarilgan obyekt — bu oddiy JS obyekt, va uni import qilganda siz shu obyektga (yoki uning property'siga) qiymat sifatida ega bo'lasiz:

// counter.js (CommonJS)
let count = 0;
function increment() { count++; }

module.exports = { count, increment };
//                 ^^^^^ bu yerda count'ning HOZIRGI qiymati (0) nusxalanadi,
//                 keyin count o'zgarsa ham, bu eksport qilingan `count` o'zgarmaydi

// main.js
const { count, increment } = require('./counter.js');

console.log(count); // 0
increment();
console.log(count); // hali ham 0! ❌ ESM'dagidek jonli bog'lanish yo'q

Buning sababi oddiy: module.exports = { count, increment } yozilganda, count — bu obyekt yaratilayotgan paytdagi primitiv qiymat (0)ning nusxasi, obyektning property'si sifatida saqlanadi. Keyin counter.js ichida count++ bo'lganda, bu faqat o'sha modul ichidagi lokal count o'zgaruvchisini o'zgartiradi — eksport qilingan obyektning count property'siga hech qanday aloqasi qolmagan (agar increment funksiyasi alohida getCount() metodi orqali countni qaytarmasa).


Xulosa jadvali — CommonJS vs ESM

Xususiyat CommonJS ES Modules
Yuklash Sinxron (require) Asinxron (native), statik tuzilma
Qachon aniqlanadi Runtime (dinamik) Parse-time (statik)
Bog'lanish turi Qiymat nusxasi Live binding (jonli)
Shart ichida ishlatish ✅ mumkin ❌ mumkin emas (faqat import() dinamik funksiya orqali)
Muhit Node.js (asosan) Brauzer + zamonaviy Node.js
Tree shaking Ishonchli qilib bo'lmaydi To'liq qo'llab-quvvatlanadi