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 |