JavaScript үндэс / Closure

Closure

Closure бол JavaScript-ийн хамгийн хүчирхэг бөгөөд анх сонсоход хамгийн гайхмаар ойлголтуудын нэг. Олон шинэ программист closure-тай танилцаад "энэ юу вэ?" гэж толгой эргэдэг — тэгэхдээ аажмаар уншаарай, жишээнүүд бүгдийг тодруулна.

Энгийнээр тайлбарлавал: closure нь функц өөрийгөө үүсгэсэн орчны хувьсагчдыг тэр орчин устсаны дараа ч санаж үлддэг үзэгдэл юм.

Scope-аас эхлэх

Өмнөх хичээлд функц дотор зарласан хувьсагч гаднаас харагдахгүй гэж сурсан. Харин дотор функц гаднах хувьсагчийг харж чадна — энэ бол closure-ын эхлэл.

javascript
function outer() {
  const message = "Би гаднах функц"; // outer-ийн хувьсагч

  function inner() {
    console.log(message); // ✅ inner нь message-г харж чадна
  }

  inner();
}

outer(); // Би гаднах функц

Энэ хэвийн — scope chain-аар ажилладаг. Closure үүсдэг тохиолдол нь дотор функцийг гаднаас буцаах үед.

Closure үүсэх — функц буцаасан функц

javascript
function makeCounter() {
  let count = 0; // makeCounter-ийн local хувьсагч

  return function () {
    count++;
    console.log(count);
  };
}

const counter = makeCounter();

counter(); // 1
counter(); // 2
counter(); // 3

makeCounter() дуусмагц count хувьсагч устах ёстой — гэтэл устаагүй! Буцаагдсан функц count-д хандах эрхийг хадгалсаар байна. Энэ бол closure.

counter бол makeCounter-ийн count хувьсагчийг санаж буй функц. Тус тусдаа makeCounter() дуудах бүрт шинэ, бие даасан count үүсдэг:

javascript
const counter1 = makeCounter();
const counter2 = makeCounter();

counter1(); // 1
counter1(); // 2
counter2(); // 1  ← counter2-ийн өөрийн count — counter1-д нөлөөлөхгүй
counter1(); // 3

Closure-ын практик хэрэглээ

Closure нь хувийн (private) өгөгдөл үүсгэхэд хэрэглэгддэг — гаднаас шууд хандаж өөрчлөх боломжгүй, зөвхөн зөвшөөрөгдсөн функцээр дамжуулан удирдагдана.

javascript
function createPlayer(name) {
  let xp = 0; // private — гаднаас шууд харагдахгүй
  let level = 1; // private

  return {
    addXP: function (amount) {
      xp += amount;
      if (xp >= level * 100) {
        level++;
        console.log(`🎉 ${name} түвшин ахиллаа! Одоо ${level}-р түвшин.`);
      }
    },
    getStats: function () {
      console.log(`${name} — Түвшин: ${level}, XP: ${xp}`);
    },
  };
}

const player = createPlayer("Болд");
player.addXP(60);
player.getStats(); // Болд — Түвшин: 1, XP: 60

player.addXP(50);
// 🎉 Болд түвшин ахиллаа! Одоо 2-р түвшин.
player.getStats(); // Болд — Түвшин: 2, XP: 110

console.log(player.xp); // undefined — шууд хандаж болохгүй
console.log(player.level); // undefined — хамгаалагдсан

Closure ба давталт — нийтлэг алдаа

Closure-тай холбоотой нийтлэг нэг алдааг ойлгох нь чухал:

javascript
// ❌ var ашиглах — бүгд нэг i-г хуваалцана
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i); // Бүгд 3 хэвлэнэ!
  }, 100);
}
// 3, 3, 3

// ✅ let ашиглах — блок scope, closure тус бүр өөр i-тэй
for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i); // 0, 1, 2
  }, 100);
}
// 0, 1, 2

let нь давталт бүрд шинэ i үүсгэдэг учраас closure тус бүр өөр өөр i-г санана. Энэ бол letvar-ын оронд ашиглах чухал шалтгаануудын нэг.

Дараагийн хичээлд:

Массив (Array) — олон утгыг дарааллаар хадгалах өгөгдлийн бүтцийг судлана. Үүсгэх, нэмэх, хасах, индексээр хандах бүгдийг үзнэ.