2469 words
12 minutes
Closure in JavaScript

إيه هو الـ Closure أصلاً؟ 🤔#

الـ Closure من أهم المفاهيم في JavaScript، وهو موجود من أول ما اللغة ظهرت في 1995. الفكرة ظهرت عشان تحل مشكلة كبيرة: إزاي نحافظ على البيانات private في اللغة، خصوصاً إن JavaScript ماكانش فيها classes ولا private variables في الأول.

تعريف بسيط 📚#

الـ Closure ببساطة هو لما فانكشن داخلية “تفتكر” المتغيرات اللي كانت موجودة في الفانكشن الأم وقت تعريفها، حتى بعد ما الفانكشن الأم تخلص شغلها. دي ميزة قوية جداً بتخلينا نعمل حاجات كتير مهمة في الـ JavaScript.

ليه الـ Closure مهم؟ 🎯#

  1. Data Privacy: بيساعدنا نخلي البيانات private ومانخليش حد يوصلها من برة
  2. State Management: بيحافظ على state بين الـ function calls
  3. Module Pattern: بيساعدنا نعمل modules منظمة في الكود
  4. Event Handlers: بيستخدم كتير في الـ callbacks والـ event handlers

مثال تفصيلي للفهم 🔍#

خلينا نفهم الـ Closure بمثال بسيط ونشرحه خطوة خطوة:

function outerFunction(x) {
    let name = "Ahmed";  // متغير private في الفانكشن الخارجي
  
    function innerFunction() {
        // الفانكشن الداخلي بيكون عنده "closure" على المتغيرات المحيطة
        console.log(name);  // يقدر يوصل للمتغير name
        console.log(x);     // ويقدر يوصل للباراميتر x
    }
  
    return innerFunction;  // بنرجع الفانكشن الداخلي
}

const myFunction = outerFunction("مرحباً");
myFunction();  // هيطبع: Ahmed و مرحباً

// لو حاولنا نوصل للمتغير name من برة
console.log(name);  // ⚠️ Error: name is not defined

شرح المثال خطوة خطوة 📝#

  1. إنشاء الـ Scope:
    • لما بننفذ outerFunction، بيتعمل scope جديد فيه المتغير name والباراميتر x
    • المتغيرات دي private ومش موجودة برة الـ function
┌─── Global Scope ──────────────────┐
│                                   │
│   ┌─── outerFunction Scope ───┐   │
│   │                           │   │
│   │   let name = "Ahmed"      │   │
│   │   parameter x = "مرحباً"   │   │
│   │                           │   │
│   │   ┌─ innerFunction ─┐     │   │
│   │   │                 │     │   │
│   │   │  closure       │     │   │
│   │   │  ↑ name        │     │   │
│   │   │  ↑ x          │     │   │
│   │   └─────────────────┘     │   │
│   └───────────────────────────┘   │
└───────────────────────────────────┘
  1. الـ Closure في العمل:
    • الـ innerFunction بتعمل closure على الـ scope بتاع outerFunction
    • يعني بتحتفظ بمرجع (reference) للمتغيرات اللي محتاجاها (name و x)
    • حتى بعد ما outerFunction تخلص، الـ closure بيفضل محتفظ بالقيم دي
┌─── بعد تنفيذ outerFunction ───────┐
│                                   │
│   myFunction ──────┐              │
│                   ↓               │
│   ┌─── Closure ───────────────┐   │
│   │                           │   │
│   │   name = "Ahmed"          │   │
│   │   x = "مرحباً"            │   │
│   │                           │   │
│   │   function() {           │   │
│   │     console.log(name)    │   │
│   │     console.log(x)       │   │
│   │   }                      │   │
│   └───────────────────────────┘   │
└───────────────────────────────────┘
  1. الـ Lexical Scoping:
    • JavaScript بيستخدم نظام الـ Lexical Scoping
    • يعني الفانكشن بتقدر توصل للمتغيرات:
      • اللي معرفة جواها
      • واللي معرفة في أي scope برة منها
    • لكن مش العكس! الـ scope الخارجي مش بيقدر يوصل للمتغيرات جوة الـ functions
┌─── Lexical Scoping ──────────────────────┐
│                                          │
│ Global Scope                             │
│ │                                        │
│ ├─── outerFunction                       │
│ │    │                                   │
│ │    ├─── name, x                        │
│ │    │     (متاح للـ innerFunction)      │
│ │    │                                   │
│ │    └─── innerFunction                  │
│ │          (يقدر يوصل للمتغيرات فوق)     │
│ │                                        │
└──────────────────────────────────────────┘

الـ Lexical Scope والـ Closure: العلاقة بينهم 🤝#

إيه هو الـ Lexical Scope? 🔍#

الـ Lexical Scope هو القواعد اللي بتحدد: “مين يقدر يشوف مين؟” في الكود. يعني زي خريطة بتقول للفانكشن: “أنت ممكن تشوف المتغيرات دي”.

// مثال بسيط للـ Lexical Scope
let name = "Ahmed";  // متغير في الـ global scope

function sayHi() {
    let greeting = "Hi";  // متغير في الـ local scope
    console.log(greeting + " " + name);  // يقدر يشوف الاتنين
}

sayHi();  // "Hi Ahmed"
// console.log(greeting);  // ❌ Error! مش هيشتغل

العلاقة بين الـ Lexical Scope والـ Closure 🔄#

الـ Lexical Scope هو الأساس اللي بيخلي الـ Closure يشتغل! تخيل الموضوع كده:

  1. الـ Lexical Scope بيحدد: “مين يقدر يشوف مين؟”
  2. الـ Closure بيقول: “طيب خليني أحفظ اللي أنا شايفه ده”
function createGame() {
    let score = 0;  // متغير في الـ lexical scope بتاع createGame
    
    // الفانكشن دي عندها closure على score
    function play() {
        score++;  // بتقدر تشوف وتعدل score بسبب الـ lexical scope
        console.log(`Score: ${score}`);
    }
    
    return play;
}

const game = createGame();
game();  // Score: 1
game();  // Score: 2

تشبيه للفهم 📦#

تخيل الموضوع كده:

┌─── المكتب (Global Scope) ───────────┐
│                                     │
│   ┌─── الدرج (Lexical Scope) ───┐   │
│   │                             │   │
│   │   المتغيرات                 │   │
│   │   - score                   │   │
│   │                             │   │
│   │   ┌─── Closure ─────┐       │   │
│   │   │                 │       │   │
│   │   │  "أنا فاكر كل  │       │   │
│   │   │  حاجة في الدرج" │       │   │
│   │   │                 │       │   │
│   │   └─────────────────┘       │   │
│   └─────────────────────────────┘ │
└────────────────────────────────────┘

النقط المهمة 🎯#

  1. الـ Lexical Scope:

    • بيحدد نطاق رؤية المتغيرات
    • ثابت ومعروف وقت كتابة الكود
    • بيتحكم في مين يقدر يشوف مين
  2. الـ Closure:

    • بيستفيد من الـ Lexical Scope
    • بيحفظ المتغيرات اللي هو شايفها
    • بيخلي المتغيرات دي private
    • بيفضل فاكر القيم حتى بعد ما الفانكشن الأصلية تخلص
  3. العلاقة بينهم:

    • الـ Lexical Scope هو الأساس
    • الـ Closure هو اللي بيستخدم الأساس ده
    • مع بعض بيعملوا نظام قوي للـ data privacy والـ state management

إزاي الـ Closure بيشتغل ؟ 🔧#

1. الـ Closure في الميموري 🧠#

تخيل معايا إن الـ Closure زي صندوق بيحتفظ بحاجتين:

  1. الفانكشن نفسها
  2. كل المتغيرات اللي الفانكشن محتاجاها من برة
function makeCounter() {
    let x = 1;
    let y = 2;
    
    return function counter() {
        // بنستخدم المتغيرات
        return x + y;
    };
}

// لما نعمل كده:
const counter = makeCounter();

في الميموري، بيتعمل صندوق كده:

┌─── صندوق الـ Closure ───┐
│                         │
│  ┌── المتغيرات ────┐    │
│  │  x = 1          │    │
│  │  y = 2          │    │
│  └────────────────┘     │
│                         │
│  ┌── الفانكشن ─────┐    │
│  │  counter()     │    │
│  │  {return x + y} │    │
│  └────────────────┘     │
└─────────────────────────┘

2. ليه الصندوق ده مهم؟ 🤔#

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

counter1();  // 3
counter2();  // 3

كل ما نعمل counter جديد، بيتعمل صندوق جديد:

┌─── counter1 ───┐    ┌─── counter2 ───┐
│  x = 1         │    │  x = 1         │
│  y = 2         │    │  y = 2         │
│  counter()     │    │  counter()     │
└───────────────┘    └───────────────┘

3. إزاي بيشتغل في الميموري؟ 💭#

function greet(name) {
    let message = "مرحباً ";  // متغير محلي
    
    return function() {
        // بيستخدم المتغيرات اللي برة
        console.log(message + name);  
    }
}

const sayHiAhmed = greet("أحمد");

لما الكود ده بيشتغل:

  1. greet بتتنفذ وبتعمل المتغيرات بتاعتها
  2. قبل ما ترجع الفانكشن، بتعمل “صورة” للمتغيرات اللي هنحتاجها
  3. الفانكشن اللي راجعة بتاخد معاها الصورة دي
قبل Return:
┌─── greet ───────────┐
│ message = "مرحباً"  │
│ name = "أحمد"      │
└──────────────────┘

بعد Return:
┌─── sayHiAhmed ──────┐
│ "صورة" المتغيرات:   │
│ message = "مرحباً"  │
│ name = "أحمد"      │
└──────────────────┘

4. إيه اللي بيحصل لو عدلنا المتغيرات؟ 🔄#

function createUpdater() {
    let value = 0;
    
    return {
        getValue: function() { return value; },
        increment: function() { value++; },
        updateValue: function(newValue) { value = newValue; }
    };
}

const updater = createUpdater();

كل الفانكشنز بتشترك في نفس النسخة من value:

┌─── updater ────────────────┐
│                            │
│  value = 0                 │
│  │                         │
│  ├─► getValue()            │
│  ├─► increment()           │
│  └─► updateValue()         │
│                            │
└────────────────────────────┘

لما نستخدمه:

updater.getValue();    // 0
updater.increment();   // value بقت 1
updater.updateValue(5);// value بقت 5
updater.getValue();    // 5

الـ Closure في الـ JavaScript Engine 🛠️#

أ. خطوات تنفيذ الـ Closure في الـ V8 Engine#

function createAuthSystem() {
    let token = null;
    let user = null;
    
    return {
        login(username, password) {
            // محاكاة عملية تسجيل الدخول
            token = `token_${username}_${Date.now()}`;
            user = { username, lastLogin: new Date() };
            return true;
        },
        
        isAuthenticated() {
            return token !== null;
        },
        
        getUserInfo() {
            if (!token) return null;
            return { ...user };
        },
        
        logout() {
            token = null;
            user = null;
        }
    };
}

const auth = createAuthSystem();
auth.login('ahmed', '123456');
console.log(auth.isAuthenticated());  // true
console.log(auth.getUserInfo());      // { username: 'ahmed', lastLogin: ... }

تعالوا نفهم بالظبط إزاي الـ V8 Engine بيتعامل مع الـ Closure من خلال المثال العملي اللي فات دا:

  1. مرحلة التحليل (Parsing) 📝 المحرك بيقرأ الكود ويحلله:
┌── النطاق العام (Global) ─────────────────┐
│                                          │
│   ┌── نطاق createAuthSystem ──────────────┐ │
│   │                                    │ │
│   │   token = null                      │ │
│   │   user = null                       │ │
│   │   │                                │ │
│   │   └─► login()                       │ │
│   │        - بيقدر يقرأ token         │ │
│   │        - بيقدر يعدل token         │ │
│   │        - بيقدر يقرأ user         │ │
│   │        - بيقدر يعدل user         │ │
│   │                                    │ │
│   │   ┌─► isAuthenticated()          │ │
│   │   │  - بيقدر يقرأ token         │ │
│   │   └───────────────────────────────┘ │
│   │                                    │ │
│   │   ┌─► getUserInfo()               │ │
│   │   │  - بيقدر يقرأ token         │ │
│   │   │  - بيقدر يقرأ user         │ │
│   │   └───────────────────────────────┘ │
│   │                                    │ │
│   │   ┌─► logout()                     │ │
│   │   │  - بيقدر يعدل token         │ │
│   │   │  - بيقدر يعدل user         │ │
│   │   └───────────────────────────────┘ │
│   └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
  1. مرحلة إنشاء النطاقات (Scope Creation) 🏗️
// بناء سلسلة النطاقات
{
    scopes: {
        login: {
            // login بيشوف كل حاجة في createAuthSystem
            parent: 'createAuthSystem',
            type: 'function',
            variables: []
        },
        isAuthenticated: {
            // isAuthenticated بيشوف كل حاجة في createAuthSystem
            parent: 'createAuthSystem',
            type: 'function',
            variables: []
        },
        getUserInfo: {
            // getUserInfo بيشوف كل حاجة في createAuthSystem
            parent: 'createAuthSystem',
            type: 'function',
            variables: []
        },
        logout: {
            // logout بيشوف كل حاجة في createAuthSystem
            parent: 'createAuthSystem',
            type: 'function',
            variables: []
        },
        createAuthSystem: {
            // createAuthSystem بيشوف النطاق العام
            parent: 'global',
            type: 'function',
            variables: ['token', 'user', 'login', 'isAuthenticated', 'getUserInfo', 'logout']
        }
    }
}
  1. مرحلة تجهيز البيئة (Environment Setup) ⚙️
// تجهيز بيئة التشغيل
{
    environments: {
        createAuthSystem: {
            variables: {
                token: {
                    value: null,
                    type: 'string',
                    scope: 'private'
                },
                user: {
                    value: null,
                    type: 'object',
                    scope: 'private'
                }
            },
            closure: {
                login: {
                    capturedVars: ['token', 'user']
                },
                isAuthenticated: {
                    capturedVars: ['token']
                },
                getUserInfo: {
                    capturedVars: ['token', 'user']
                },
                logout: {
                    capturedVars: ['token', 'user']
                }
            }
        }
    }
}
  1. مرحلة التنفيذ (Execution) ⚡️
┌── ذاكرة التشغيل ──────────────────────────┐
│                                           │
│   ┌── بيئة login ──────────────────┐     │
│   │                                │     │
│   │   ┌── المتغيرات المحمية ──────┐  │     │
│   │   │     token = null          │  │     │
│   │   │     user = null           │  │     │
│   │   └───────────────────────────┘  │     │
│   │                                │     │
│   │   ┌── النطاق ──────────────────┐  │     │
│   │   │     createAuthSystem       │  │     │
│   │   │         ↓                  │  │     │
│   │   │      global               │  │     │
│   │   └──────────────────────────────┘  │     │
│   └────────────────────────────────────┘     │
│                                           │
│   ┌── بيئة isAuthenticated ──────┐     │
│   │                                │     │
│   │   ┌── المتغيرات المحمية ──────┐  │     │
│   │   │     token = null          │  │     │
│   │   └───────────────────────────┘  │     │
│   │                                │     │
│   │   ┌── النطاق ──────────────────┐  │     │
│   │   │     createAuthSystem       │  │     │
│   │   │         ↓                  │  │     │
│   │   │      global               │  │     │
│   │   └──────────────────────────────┘  │     │
│   └────────────────────────────────────┘     │
│                                           │
│   ┌── بيئة getUserInfo ────────┐     │
│   │                                │     │
│   │   ┌── المتغيرات المحمية ──────┐  │     │
│   │   │     token = null          │  │     │
│   │   │     user = null           │  │     │
│   │   └───────────────────────────┘  │     │
│   │                                │     │
│   │   ┌── النطاق ──────────────────┐  │     │
│   │   │     createAuthSystem       │  │     │
│   │   │         ↓                  │  │     │
│   │   │      global               │  │     │
│   │   └──────────────────────────────┘  │     │
│   └────────────────────────────────────┘     │
│                                           │
│   ┌── بيئة logout ──────────────┐     │
│   │                                │     │
│   │   ┌── المتغيرات المحمية ──────┐  │     │
│   │   │     token = null          │  │     │
│   │   │     user = null           │  │     │
│   │   └───────────────────────────┘  │     │
│   │                                │     │
│   │   ┌── النطاق ──────────────────┐  │     │
│   │   │     createAuthSystem       │  │     │
│   │   │         ↓                  │  │     │
│   │   │      global               │  │     │
│   │   └──────────────────────────────┘  │     │
│   └────────────────────────────────────┘     │
└─────────────────────────────────────────────┘

أمثلة عملية من العالم الحقيقي 🌍#

١. نظام المصادقة (Authentication System)#

function createAuthSystem() {
    let token = null;
    let user = null;
    
    return {
        login(username, password) {
            // محاكاة عملية تسجيل الدخول
            token = `token_${username}_${Date.now()}`;
            user = { username, lastLogin: new Date() };
            return true;
        },
        
        isAuthenticated() {
            return token !== null;
        },
        
        getUserInfo() {
            if (!token) return null;
            return { ...user };
        },
        
        logout() {
            token = null;
            user = null;
        }
    };
}

const auth = createAuthSystem();
auth.login('ahmed', '123456');
console.log(auth.isAuthenticated());  // true
console.log(auth.getUserInfo());      // { username: 'ahmed', lastLogin: ... }

٢. نظام إدارة الحالة (State Management)#

function createAppState() {
    const state = {
        theme: 'light',
        language: 'ar',
        notifications: []
    };
    
    const listeners = new Set();
    
    return {
        getState() {
            return { ...state };
        },
        
        setState(updates) {
            Object.assign(state, updates);
            listeners.forEach(listener => listener(state));
        },
        
        subscribe(listener) {
            listeners.add(listener);
            return () => listeners.delete(listener);
        }
    };
}

const appState = createAppState();
const unsubscribe = appState.subscribe(state => {
    console.log('التحديث:', state);
});

appState.setState({ theme: 'dark' });  // التحديث: { theme: 'dark', ... }

٣. مكتبة لإدارة النماذج (Form Library)#

function createFormManager(initialValues = {}) {
    let values = { ...initialValues };
    let errors = {};
    let touched = {};
    
    return {
        setValue(field, value) {
            values[field] = value;
            touched[field] = true;
            this.validate(field);
        },
        
        getValue(field) {
            return values[field];
        },
        
        validate(field) {
            const value = values[field];
            errors[field] = !value ? 'هذا الحقل مطلوب' : '';
        },
        
        getErrors() {
            return { ...errors };
        },
        
        isValid() {
            return Object.values(errors).every(error => !error);
        },
        
        reset() {
            values = { ...initialValues };
            errors = {};
            touched = {};
        }
    };
}

const form = createFormManager({ username: '', email: '' });
form.setValue('username', 'ahmed');
console.log(form.getValue('username'));  // 'ahmed'
console.log(form.isValid());            // false (email still empty)

٤. نظام التخزين المؤقت الذكي (Smart Caching System)#

function createSmartCache() {
    const cache = new Map();
    const expiryTimes = new Map();
    
    // تنظيف الكاش كل دقيقة
    setInterval(() => {
        const now = Date.now();
        for (const [key, expiry] of expiryTimes) {
            if (now > expiry) {
                cache.delete(key);
                expiryTimes.delete(key);
            }
        }
    }, 60000);
    
    return {
        set(key, value, ttlMinutes = 5) {
            cache.set(key, value);
            expiryTimes.set(key, Date.now() + (ttlMinutes * 60000));
        },
        
        get(key) {
            if (!cache.has(key)) return null;
            if (Date.now() > expiryTimes.get(key)) {
                this.delete(key);
                return null;
            }
            return cache.get(key);
        },
        
        delete(key) {
            cache.delete(key);
            expiryTimes.delete(key);
        },
        
        clear() {
            cache.clear();
            expiryTimes.clear();
        }
    };
}

const cache = createSmartCache();
cache.set('user:123', { name: 'Ahmed' }, 10);  // تخزين لمدة 10 دقائق
console.log(cache.get('user:123'));            // { name: 'Ahmed' }

٥. نظام إدارة الأحداث المتقدم (Advanced Event System)#

function createEventManager() {
    const events = new Map();
    const onceCallbacks = new Set();
    
    return {
        on(event, callback) {
            if (!events.has(event)) {
                events.set(event, new Set());
            }
            events.get(event).add(callback);
            
            // إرجاع دالة لإلغاء الاشتراك
            return () => this.off(event, callback);
        },
        
        once(event, callback) {
            const wrappedCallback = (...args) => {
                callback(...args);
                this.off(event, wrappedCallback);
            };
            onceCallbacks.add(wrappedCallback);
            return this.on(event, wrappedCallback);
        },
        
        off(event, callback) {
            if (events.has(event)) {
                events.get(event).delete(callback);
                onceCallbacks.delete(callback);
            }
        },
        
        emit(event, ...args) {
            if (events.has(event)) {
                events.get(event).forEach(callback => {
                    try {
                        callback(...args);
                    } catch (error) {
                        console.error(`خطأ في معالج الحدث ${event}:`, error);
                    }
                });
            }
        },
        
        clear() {
            events.clear();
            onceCallbacks.clear();
        }
    };
}

const eventManager = createEventManager();
eventManager.on('userLogin', user => {
    console.log('تم تسجيل دخول:', user);
});

eventManager.once('appStart', () => {
    console.log('تم بدء التطبيق - هذه الرسالة تظهر مرة واحدة فقط');
});

eventManager.emit('userLogin', { id: 1, name: 'Ahmed' });
eventManager.emit('appStart');
eventManager.emit('appStart');  // لن تظهر الرسالة مرة أخرى

هذه الأمثلة توضح كيف يمكن استخدام الـ Closure في تطبيقات حقيقية لتحقيق:

  • Data Encapsulation
  • Information Hiding
  • State Management
  • Smart Caching
  • Advanced Event System

Join our whatsapp group here
My Channel here