Dərs 12 Orta səviyyə

Closures və dərhal icra olunan funksiyalar

JavaScript-də closure mexanizmini, leksik mühiti, scope chain persistence, IIFE pattern və yaddaş idarəetməsini ətraflı öyrənin.

Bu dərsdə öyrənəcəksiniz

  • Closure nədir və necə işləyir
  • Leksik mühit və scope chain persistence
  • Closure-ların praktik istifadə halları (data privacy, factory functions)
  • IIFE pattern və module pattern
  • Closure-ların yaddaşa təsiri və best practices

01 Closure nədir?

Closure JavaScript-in ən güclü və maraqlı xüsusiyyətlərindən biridir. Closure funksiyaya yaradıldığı leksik mühitə (scope) çatmaq imkanı verir, hətta xarici funksiya icrasını başa vurduqdan sonra belə.

Tərif:Closure funksiya ilə onun yaradıldığı leksik mühitin birləşməsidir. Başqa sözlə, daxili funksiya xarici funksiyanın dəyişənlərinə, hətta xarici funksiya return edildikdən sonra belə çata bilir.

Sadə closure nümunəsi

Gəlin ən sadə closure nümunəsinə baxaq:

javascript
function outerFunction() {
  let outerVariable = "I'm from outer";

  function innerFunction() {
    console.log(outerVariable); // Xarici dəyişənə çatır
  }

  return innerFunction;
}

const myFunction = outerFunction();
myFunction(); // I'm from outer

Bu necə işləyir:

  1. outerFunction icra olunur və innerFunction-u qaytarır
  2. outerFunction icrasını bitirir, amma onun dəyişənləri (outerVariable) yaddaşda qalır
  3. myFunction əslində innerFunction-dur
  4. myFunction çağırılanda o, hələ də outerVariable-a çata bilir (closure vasitəsilə)

Closure-lar necə işləyir

Hər closure öz yaradıldığı dəyişənlərin ayrı kopiyasını saxlayır:

javascript
function createGreeting(name) {
  return function() {
    console.log("Hello, " + name + "!");
  };
}

const greetJohn = createGreeting("John");
const greetMary = createGreeting("Mary");

greetJohn(); // Hello, John!
greetMary(); // Hello, Mary!

// Hər funksiya öz name parametrini saxlayır
Əsas anlayış:Closure funksiyaya xarici scope-dakı dəyişənlərə müraciət etmək imkanı verir. Hər dəfə xarici funksiya çağırılanda yeni closure yaranır.

02 Leksik mühit

Closure-ları daha yaxşı başa düşmək üçün leksik mühit (lexical environment) anlayışını bilmək lazımdır.

Tərif:Leksik mühit dəyişənlərin, funksiyaların və xarici scope-a istinadın saxlandığı daxili struktur və ya obyektdir.

Leksik mühitin komponentləri

1. Environment Record

Bu hissədə bütün lokal dəyişənlər və funksiya parametrləri saxlanır.

2. Outer lexical environment istinadı

Bu istinad xarici (parent) scope-a yönəlir və scope chain yaradır.

Leksik mühit nümunəsi

Gəlin leksik mühitin necə işlədiyinə baxaq:

javascript
function outer() {
  let a = 10;
  let b = 20;

  function middle() {
    let c = 30;

    function inner() {
      let d = 40;
      // İç-içə scope-lardan bütün dəyişənlərə çatmaq mümkündür
      console.log(a + b + c + d); // 100
    }

    return inner;
  }

  return middle;
}

const middleFunc = outer();
const innerFunc = middleFunc();
innerFunc(); // 100

03 Scope chain persistence

Closure-ların ən güclü xüsusiyyətlərindən biri onların scope chain-i saxlamasıdır. Bu o deməkdir ki, daxili funksiya xarici funksiyanın dəyişənlərinə həmişə çata bilir.

Scope-un saxlanması

Xarici funksiya icrasını bitirdikdən sonra belə, onun dəyişənləri closure vasitəsilə əlçatan qalır:

javascript
function createCounter() {
  let count = 0; // Private dəyişən

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

const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3

// count dəyişəni saxlanılır və artırılır
Vacib:count dəyişəni counter funksiyası mövcud olduğu müddətdə yaddaşda qalır. Hər counter() çağırışı count dəyişəninin eyni nüsxəsinə çatır.

Çoxsaylı closure-lar

Hər dəfə xarici funksiya çağırılanda yeni closure yaranır və ayrı dəyişənlər saxlayır:

javascript
function createCounter() {
  let count = 0;

  return {
    increment: function() {
      count++;
      console.log(count);
    },
    decrement: function() {
      count--;
      console.log(count);
    },
    getCount: function() {
      return count;
    }
  };
}

const counter1 = createCounter();
const counter2 = createCounter();

counter1.increment(); // 1
counter1.increment(); // 2
counter2.increment(); // 1

console.log(counter1.getCount()); // 2
console.log(counter2.getCount()); // 1

04 Closure-ların praktik istifadəsi

Closure-lar JavaScript-də çox istifadə olunan pattern-lərdir. Gəlin əsas istifadə hallarına baxaq:

1. Data privacy (məlumatın gizliliyi)

Closure-lar private dəyişənlər yaratmaq üçün istifadə olunur - xaricdən birbaşa dəyişdirilə bilməyən dəyişənlər:

javascript
function createBankAccount(initialBalance) {
  let balance = initialBalance; // Private dəyişən

  return {
    deposit: function deposit(amount) {
      if (amount > 0) {
        balance += amount;
        console.log("Deposited: " + amount + ". Balance: " + balance);
      }
    },
    withdraw: function withdraw(amount) {
      if (amount > 0 && amount <= balance) {
        balance -= amount;
        console.log("Withdrawn: " + amount + ". Balance: " + balance);
      } else {
        console.log("Insufficient funds");
      }
    },
    getBalance: function getBalance() {
      return balance;
    }
  };
}

const myAccount = createBankAccount(1000);
myAccount.deposit(500);   // Deposited: 500. Balance: 1500
myAccount.withdraw(200);  // Withdrawn: 200. Balance: 1300
console.log(myAccount.getBalance()); // 1300

// console.log(myAccount.balance); // undefined - birbaşa çatmaq mümkün deyil!

Faydalar:

  • balance dəyişəni private-dır və birbaşa dəyişdirilə bilməz
  • Yalnız metodlar vasitəsilə balans-a çatmaq və onu dəyişmək mümkündür
  • Bu, data integrity və təhlükəsizliyi təmin edir

2. Factory functions

Closure-lar parametrlərə əsaslanan fərqli davranışa malik funksiyalar yaratmaq üçün istifadə olunur:

javascript
function createMultiplier(multiplier) {
  return function multiply(number) {
    return number * multiplier;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);

console.log(double(5));     // 10
console.log(triple(5));     // 15
console.log(quadruple(5));  // 20

05 IIFE (Immediately Invoked Function Expression)

IIFE yaradıldığı anda dərhal icra olunan funksiyadır. Bu, JavaScript-də çox istifadə olunan pattern-dir.

Tərif:IIFE təyin edildikdən dərhal sonra icra olunan funksiya ifadəsidir. Bu pattern kod encapsulation və global scope-un çirklənməsinin qarşısını almaq üçün istifadə olunur.

IIFE sintaksisi

IIFE yaratmağın iki əsas yolu var:

javascript
// Pattern 1: Funksiya mötərizədə, çağırış xaricində
(function() {
  console.log("I run immediately!");
})();

// Pattern 2: Funksiya və çağırış hər ikisi mötərizədə
(function() {
  console.log("I also run immediately!");
}());

// Parametrlərlə IIFE
(function(name) {
  console.log("Hello, " + name + "!");
})("World");

// Private scope yaradır
(function() {
  const message = "This is private";
  console.log(message);
})();

// console.log(message); // ReferenceError: message is not defined

Hər iki pattern eyni işi görür:

  • (function() {...})() - Funksiya ifadəsi mötərizələrlə əhatə olunur
  • (function() {...}()) - Funksiya ifadəsi mötərizələrlə əhatə olunur və çağırış mötərizələri də daxildir

Nə üçün IIFE istifadə edirik?

IIFE-nin bir neçə vacib istifadə halı var:

  • Scope izolasiyası - IIFE daxilindəki dəyişənlər xaricdən əlçatan olmur
  • Global scope-u çirkləndirməmək - Global dəyişənlərin sayını azaltmaq
  • Data privacy - Private dəyişənlər və metodlar yaratmaq
  • Təkrar istifadə olunmayan kod - Bir dəfə icra olunmalı olan kod üçün ideal
javascript
// IIFE-siz: global scope-u çirkləndirir
var counter = 0;
function increment() {
  counter++;
}
function getCounter() {
  return counter;
}

// Bütün dəyişənlər və funksiyalar global-dır

// IIFE ilə: təmiz və encapsulated
const counterModule = (function() {
  let counter = 0; // Private

  return {
    increment: function() {
      counter++;
    },
    getCounter: function() {
      return counter;
    }
  };
})();

counterModule.increment();
console.log(counterModule.getCounter()); // 1
// console.log(counter); // ReferenceError: counter is not defined

Parametrlərlə IIFE

IIFE-yə parametr ötürmək mümkündür. Bu, xarici dəyişənləri lokal scope-a təhlükəsiz şəkildə gətirmək üçün istifadə olunur.

javascript
// Arqumentlərin ötürülməsi
(function(name, age) {
  console.log("Name: " + name + ", Age: " + age);
})("John", 30);

// Global dəyişəni IIFE-yə ötürmək
const globalVar = "I'm global";

(function(global) {
  console.log(global); // I'm global
})(globalVar);

06 Closure-larla bağlı tipik səhvlər

Closure-lar güclü mexanizmdir, amma bir neçə tipik səhv var ki, onlardan qaçmaq lazımdır:

❌ Loop-larda closure problemi

Ən çox rast gəlinən closure səhvi loop-larda baş verir:

javascript
// ❌ Problem: Hər dəfə 4 çıxarır
for (var i = 1; i <= 3; i++) {
  setTimeout(function() {
    console.log(i); // 4, 4, 4
  }, i * 1000);
}

// Çünki var function scope-a malikdir və hər bir iterasiyada yeni i yaranır

✅ Həll yolları:

javascript
// Həll 1: let istifadə edin (ən sadə)
for (let i = 1; i <= 3; i++) {
  setTimeout(function() {
    console.log(i); // 1, 2, 3 (düzgün)
  }, i * 1000);
}

// Həll 2: IIFE istifadə edin
for (var i = 1; i <= 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j); // 1, 2, 3 (düzgün)
    }, j * 1000);
  })(i);
}

// Həll 3: Closure factory
for (var i = 1; i <= 3; i++) {
  setTimeout((function(j) {
    return function() {
      console.log(j); // 1, 2, 3 (düzgün)
    };
  })(i), i * 1000);
}

❌ Yaddaş sızması (memory leak)

Closure-lar lazım olmayan böyük obyektləri yaddaşda saxlaya bilər:

javascript
function createHeavyObject() {
  const hugeArray = new Array(1000000).fill('data');

  return function() {
    // hugeArray bütünlüklə yaddaşda qala bilər.
    console.log(hugeArray.length);
  };
}

// Qaytarılan funksiya mövcud olduğu müddətdə yaddaşda qalacaq
const func = createHeavyObject();

// Daha yaxşı yol:
function createLightObject() {
  const hugeArray = new Array(1000000).fill('data');
  const length = hugeArray.length; // Yalnız lazım olanı saxlayın

  return function() {
    console.log(length); // İndi yalnız length saxlanılır, array yox
  };
}
Diqqət:Closure yalnız istifadə etdiyi dəyişəni saxlamalıdır. Əgər böyük obyekt varsa və siz onun kiçik bir hissəsini istifadə edirsinizsə, yalnız lazım olan hissəni ayrıca dəyişənə yazın.

07 Closure-ların yaddaşa təsiri

Closure-lar güclüdür, amma onların yaddaşa təsirini başa düşmək vacibdir.

Closure-lar yaddaşı necə təsir edir

  • Closure yarandıqda, o öz leksik mühitinə istinad saxlayır
  • Nəzəri olaraq, closure istifadə etdiyi bütün dəyişənlər yaddaşda qalır
  • Müasir JavaScript engine-ləri çox vaxt istifadə olunmayan dəyişənləri optimizasiya edə bilər, lakin bu zəmanət deyil
  • Əmin olmaq üçün yalnız lazım olan dəyərləri closure-da saxlayın
javascript
function outer() {
  const largeData = new Array(10000).fill('data');
  const smallData = 'small';

  return function inner() {
    // inner funksiyası yalnız smallData istifadə edir,
    // amma closure öz leksik mühitinə istinad saxlayır
    console.log(smallData);
  };
}

const func = outer();
// Nəzəri olaraq largeData da yaddaşda qala bilər (engine-dən asılıdır)
// Qeyd: Müasir JavaScript engine-ləri (V8, SpiderMonkey) çox vaxt istifadə olunmayan dəyişənləri optimizasiya edir və silir. Lakin bu zəmanət deyil və bütün engine-lərdə işləməyə bilər.

// Daha yaxşı və zəmanətli yanaşma:
function betterOuter() {
  const largeData = new Array(10000).fill('data');
  const result = largeData.length; // Lazım olan dəyəri çıxarın və ayrıca saxlayın

  return function inner() {
    console.log(result); // İndi yalnız result (primitive dəyər) saxlanılır və largeData garbage collection tərəfindən silinə bilər
  };
}

Best practices

Yalnız lazım olanı saxlayın

Əgər böyük obyektdən yalnız bir dəyər lazımdırsa, onu ayrıca dəyişənə çıxarın.

Closure-ları həddindən artıq istifadə etməyin

Hər closure yaddaş istifadə edir. Çoxsaylı closure-lar performansa təsir edə bilər.

Lazım olmayan istinadları təmizləyin

Əgər closure-a daha ehtiyac yoxdursa, ona olan istinadı null-a təyin edin.

08 IIFE-yə müasir alternativlər

ES6 və sonrakı versiyalar IIFE-nin yerini tuta biləcək yeni xüsusiyyətlər təqdim etdi.

Block scope let/const ilə

Sadə scope izolasiyası üçün block scope istifadə edə bilərsiniz:

javascript
// Köhnə üsul: IIFE
(function() {
  var temp = 42;
  console.log(temp);
})();

// Müasir üsul: Block scope
{
  let temp = 42;
  console.log(temp);
}

// console.log(temp); // ReferenceError: temp is not defined

ES6 modulları

Module pattern-in yerini ES6 modulları tutdu:

javascript
// Köhnə üsul: IIFE module pattern
const myModule = (function() {
  let privateVar = 'private';

  return {
    publicMethod: function() {
      console.log(privateVar);
    }
  };
})();

// Müasir üsul: ES6 modules
// counter.js
let count = 0;

export function increment() {
  count++;
}

export function getCount() {
  return count;
}

// main.js
// import { increment, getCount } from './counter.js';
Qeyd:ES6 modulları daha yaxşı, daha təmiz və standartlaşdırılmış alternativdir. Onlar IIFE əsaslı module pattern-i demək olar ki, tamamilə əvəz etdi. Bu haqqda növbəti dərslərdə detallı şəkildə danışılacaq.

IIFE-ni hələ də nə vaxt istifadə etmək olar

  • Köhnə browser-ləri dəstəkləmək lazım olduqda
  • Module sistemi olmayan mühitlərdə
  • Bir dəfə icra olunmalı olan initialization kodu üçün
  • Köhnə kod bazalarını support edərkən

Xülasə

Closure funksiyaya yaradıldığı leksik mühitə çatmaq imkanı verir
Leksik mühit dəyişənləri və xarici scope-a istinadı saxlayır
Closure-lar data privacy, factory functions və event handler-lər üçün istifadə olunur
IIFE təyin edildikdən dərhal sonra icra olunan funksiyadır
IIFE scope izolasiyası və module pattern yaratmaq üçün istifadə olunur
Closure-lar yaddaşda dəyişənləri saxlayır və memory leak-ə səbəb ola bilər
ES6 modulları və block scope IIFE-yə müasir alternativlərdir

Təklifiniz var, səhv və ya xəta tapdınız? Zəhmət olmasa bizimlə əlaqə saxlayın.