نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
المقالات/JavaScript
JavaScript

Closures في JavaScript: السر الخفي وراء الكود الذكي وكيف تتقنه دون ارتباك

اكتشف كيف تعمل Closures في JavaScript خلف الكواليس، ولماذا هي أقوى من مجرد حفظ المتغيرات. أمثلة حقيقية من الإنتاج تكشف الفخاخ والأسرار التي لا يخبرك بها الدروس التقليدية.

فريق نوفيل٤ أغسطس ٢٠٢٦8 دقائق قراءة١١ مشاهدة

في أحد الأيام، كان السيرفر يعلق كل ساعة دون سبب واضح. بعد ساعات من التنقيب في الأكواد، وجدنا أن دالة صغيرة داخل loop كانت تحتفظ بمرجع لمتغير خارجي دون قصد. النتيجة؟ ٥٠٠ طلب متزامن وكل واحد منهم يحجز مساحة في الذاكرة. المشكلة لم تكن في المنطق، بل في شيء اسمه Closure. إذا كنت تعتقد أنك تعرف الـ Closures لأنك حفظت المثال الكلاسيكي عن counter، فأنت على وشك اكتشاف جانب آخر مظلم منها.

الـ Closures ليست مجرد ميزة لغوية جميلة في JavaScript، بل هي آلية عمل أساسية تتحكم في كيفية تعامل المحرك مع الذاكرة والبيانات. في هذا المقال، لن نتحدث عن التعريف الأكاديمي، بل سنفكك كيف تعمل الـ Closures على مستوى الـ Execution Context، وكيف يمكن أن تكون سلاحاً ذا حدين: إما تبني بها كوداً ذكياً ومرناً، أو تخلق بها مشاكل أداء لا تُحل بسهولة. سنبدأ من الصفر، لكن سننتهي عند مستوى يسمح لك بفهم الكود الذي يكتبه مهندسو Meta وNetflix خلف مكتباتهم الشهيرة.

ما هي الـ Closure حقاً؟ أكثر من مجرد حفظ للمتغيرات

الـ Closure ليست مجرد دالة داخل دالة تحتفظ بمتغير خارجي. هذه هي النتيجة، لكن الآلية الحقيقية أعمق بكثير. عندما تنشئ دالة داخل دالة أخرى في JavaScript، فإن المحرك لا يحفظ فقط المتغيرات الخارجية التي تستخدمها الدالة الداخلية، بل يحفظ كامل الـ Lexical Environment الذي كانت الدالة الداخلية موجودة فيه عند إنشائها. هذا يعني أن الدالة الداخلية تحتفظ بمرجع لكل ما كان متاحاً في نطاق الدالة الخارجية لحظة إنشائها، حتى لو انتهت الدالة الخارجية وعادت إلى مكدس الاستدعاءات.

لنأخذ مثالاً بسيطاً لكنه يكشف التفاصيل المخفية. في الكود التالي، الدالة `createCounter` تُرجع دالة داخلية `increment` تستخدم المتغير `count` من النطاق الخارجي. لكن ماذا يحدث بالضبط خلف الكواليس؟

javascript
function createCounter() {
 let count = 0;
 return function increment() {
 count++;
 return count;
 };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

عندما تُستدعى `createCounter`، ينشئ المحرك سياق تنفيذ جديد (Execution Context) ويحجز مساحة في الذاكرة للمتغير `count`. عند إرجاع الدالة `increment`، لا يتم إتلاف سياق التنفيذ الخارجي فوراً، بل يبقى محفوظاً في الذاكرة طالما أن الدالة `increment` ما زالت موجودة. هذا هو الـ Closure: الدالة الداخلية تحتفظ بمرجع لبيئتها الخارجية حتى بعد انتهاء تنفيذ الدالة الخارجية. إذا لم تكن الدالة الداخلية موجودة، كان المحرك سيحذف المتغير `count` بعد انتهاء `createCounter`، لكن وجود الـ Closure يمنع ذلك.

كيف تتعامل الذاكرة مع الـ Closures؟ القصة الكاملة خلف الكواليس

لفهم تأثير الـ Closures على الذاكرة، يجب أن نعرف كيف يتعامل محرك JavaScript مع الـ Memory Management. عندما تُنشئ دالة داخلية تستخدم متغيرات خارجية، ينشئ المحرك ما يسمى بـ Closure Object في الـ Heap. هذا الكائن يحتفظ بمرجع لكل المتغيرات الخارجية التي تستخدمها الدالة الداخلية، بالإضافة إلى مرجع للنطاق الخارجي بالكامل. هذا يعني أن كل مرة تنشئ فيها دالة داخلية، فإنك تحتفظ بجزء من الذاكرة لا يمكن للمجمع القمامة (Garbage Collector) مسحه طالما أن الدالة الداخلية موجودة في مكان ما في الكود.

لنرى مثالاً أكثر تعقيداً يكشف كيف يمكن للـ Closures أن تسبب تسربات في الذاكرة. في الكود التالي، نقوم بإنشاء مصفوفة من الدوال، كل واحدة منها تحتفظ بمرجع للمتغير `i` من الـ loop الخارجي:

javascript
function createFunctions() {
 const functi [];
 for (var i = 0; i < 5; i++) {
 functions.push(function() {
 console.log(i);
 });
 }
 return functions;
}

const funcs = createFunctions();
funcs[0](); // 5
funcs[1](); // 5
funcs[2](); // 5

الجميع يتوقع أن يطبع هذا الكود الأرقام من ٠ إلى ٤، لكن النتيجة هي ٥ خمس مرات. السبب؟ الـ Closure تحتفظ بمرجع للمتغير `i` نفسه، وليس بقيمته في لحظة إنشاء الدالة. عندما ينتهي الـ loop، يكون قيمة `i` تساوي ٥، وكل الدوال الداخلية تشير إلى نفس المتغير. الحل؟ استخدام `let` بدلاً من `var` لأن `let` ينشئ نطاقاً جديداً لكل تكرار في الـ loop، أو استخدام IIFE لحبس قيمة `i` في نطاق جديد:

javascript
for (var i = 0; i < 5; i++) {
 functions.push((function(value) {
 return function() {
 console.log(value);
 };
 })(i));
}

هذا المثال يكشف نقطة مهمة: الـ Closures ليست مجرد أداة لكتابة كود أنيق، بل هي آلية تؤثر بشكل مباشر على كيفية تعامل المحرك مع الذاكرة. إذا لم تفهم كيف تعمل، يمكنك بسهولة إنشاء تسربات في الذاكرة تؤدي إلى بطء في التطبيق أو حتى توقف السيرفر تحت ضغط الطلبات.


الـ Closures في الإنتاج: كيف تستخدمها الشركات الكبرى؟

الـ Closures ليست مجرد مفهوم أكاديمي، بل هي أداة أساسية في مكتبات وأطر العمل التي نستخدمها يومياً. لنأخذ مثالاً من مكتبة React الشهيرة. عندما تستخدم الـ Hook `useState`، فأنت في الحقيقة تستخدم الـ Closure لحفظ حالة المكون بين عمليات الـ Re-render. الدالة التي تُرجعها `useState` تحتفظ بمرجع للمتغير الداخلي الذي يحفظ الحالة، وهذا هو السبب في أنك تستطيع تغيير الحالة دون فقدانها عند إعادة رسم المكون.

javascript
function useState(initialValue) {
 let state = initialValue;
 function setState(newValue) {
 state = newValue;
 render(); // إعادة رسم المكون
 }
 function getState() {
 return state;
 }
 return [getState, setState];
}

هذا الكود المبسط يوضح كيف تستخدم React الـ Closure لحفظ الحالة بين عمليات الـ Re-render. الدالة `getState` تحتفظ بمرجع للمتغير `state` حتى بعد انتهاء تنفيذ `useState`، وهذا هو السبب في أن الحالة تبقى محفوظة. إذا لم تكن الـ Closure موجودة، لكانت الحالة تُفقد بعد كل عملية إعادة رسم.

مثال آخر من مكتبة Redux. عندما تنشئ middleware في Redux، فإنك تستخدم الـ Closure لحفظ مرجع للكائن `store` والدالة `next`. الـ Middleware في Redux هو مجرد دالة تُرجع دالة تُرجع دالة أخرى، وكل واحدة منها تحتفظ بمرجع للنطاق الخارجي:

javascript
const loggerMiddleware = store => next => action => {
 console.log('dispatching', action);
 let result = next(action);
 console.log('next state', store.getState());
 return result;
};

في هذا المثال، الدالة الداخلية `action => {...}` تحتفظ بمرجع للمتغيرين `store` و`next` من النطاقين الخارجيين. هذا هو السبب في أن الـ Middleware تستطيع الوصول إلى الكائن `store` حتى بعد انتهاء تنفيذ الدالة الخارجية. هذه هي قوة الـ Closures: القدرة على حفظ الحالة والبيانات بين استدعاءات الدوال المختلفة دون الحاجة إلى تمريرها كوسائط.

الفخاخ الخفية: متى تصبح الـ Closures عدواً لك؟

رغم قوة الـ Closures، إلا أنها يمكن أن تكون مصدراً لمشاكل حقيقية في الإنتاج إذا لم تُستخدم بحذر. أحد أكبر الفخاخ هو تسرب الذاكرة (Memory Leak) بسبب الاحتفاظ بمراجع لمتغيرات كبيرة دون داعٍ. لنفترض أنك تنشئ دالة داخلية تحتفظ بمرجع لمصفوفة كبيرة داخل دالة خارجية:

javascript
function processData() {
 const largeArray = new Array(1000000).fill('data');
 return function() {
 console.log(largeArray.length);
 };
}

const leakyFunction = processData();

في هذا المثال، الدالة الداخلية تحتفظ بمرجع للمصفوفة `largeArray` حتى لو لم تعد بحاجة إليها. إذا احتفظت بالدالة الداخلية في مكان ما في الكود، فإن المصفوفة الكبيرة ستبقى في الذاكرة ولن يستطيع المجمع القمامة مسحها. الحل؟ إذا كنت تعرف أنك لن تحتاج للمتغير الخارجي بعد انتهاء الدالة الخارجية، يمكنك تعيين قيمته إلى `null` لتحرير الذاكرة:

javascript
function processData() {
 const largeArray = new Array(1000000).fill('data');
 const resultFunction = function() {
 console.log(largeArray.length);
 };
 largeArray = null; // تحرير الذاكرة
 return resultFunction;
}

فخ آخر شائع هو استخدام الـ Closures داخل loops دون فهم كيفية تعامل المحرك مع المتغيرات. كما رأينا في المثال السابق، استخدام `var` داخل loop مع دالة داخلية يؤدي إلى نتائج غير متوقعة. الحل هو استخدام `let` أو IIFE لحبس قيمة المتغير في نطاق جديد. لكن حتى مع `let`، يجب أن تكون حذراً من الأداء. كل تكرار في loop ينشئ نطاقاً جديداً، وهذا يعني أن المحرك ينشئ سياق تنفيذ جديد لكل تكرار، مما قد يؤثر على الأداء في loops كبيرة.

أخيراً، يجب أن تكون حذراً عند استخدام الـ Closures مع الـ Event Listeners. إذا أنشأت دالة داخلية داخل loop وأضفتها كحدث لـ DOM Element، فإن كل دالة ستحتفظ بمرجع للمتغير الخارجي، مما قد يؤدي إلى تسرب في الذاكرة أو سلوك غير متوقع:

javascript
for (var i = 0; i < 5; i++) {
 const button = document.createElement('button');
 button.textC `Button ${i}`;
 button.addEventListener('click', function() {
 console.log(i); // سيطبع 5 دائماً
 });
 document.body.appendChild(button);
}

الحل؟ استخدام `let` أو IIFE لحبس قيمة `i` في نطاق جديد لكل تكرار. لكن حتى مع الحل، يجب أن تتذكر أن كل Event Listener تحتفظ بمرجع للدالة الداخلية، مما يعني أن الذاكرة لن تُحرر حتى يتم إزالة الـ Event Listener نفسه. إذا كنت تضيف Event Listeners داخل loop كبير، فقد تواجه مشاكل في الأداء بسبب تراكم الدوال في الذاكرة.


كيف تتقن الـ Closures؟ نصائح عملية من الميدان

إذا أردت أن تتقن الـ Closures حقاً، فلا يكفي أن تحفظ الأمثلة الكلاسيكية. يجب أن تفهم كيف تعمل خلف الكواليس، وتعرف متى تستخدمها ومتى تتجنبها. إليك بعض النصائح العملية التي تعلمتها من سنوات العمل في الإنتاج:

  • •افهم الفرق بين الـ Lexical Scope والـ Dynamic Scope. الـ Closures تعتمد على الـ Lexical Scope، مما يعني أن الدالة تحتفظ بالبيئة التي أنشئت فيها، وليس البيئة التي تُستدعى منها. هذا هو السبب في أن الدوال الداخلية تستطيع الوصول إلى المتغيرات الخارجية حتى بعد انتهاء تنفيذ الدالة الخارجية.
  • •استخدم أدوات الـ Debugging مثل Chrome DevTools لفحص الـ Closures. في لوحة Sources، يمكنك وضع نقطة توقف داخل دالة داخلية ورؤية المتغيرات التي تحتفظ بها الـ Closure في قسم Scope.
  • •تجنب الاحتفاظ بمراجع لمتغيرات كبيرة داخل الـ Closures دون داعٍ. إذا كانت الدالة الداخلية لا تحتاج إلى متغير خارجي بعد انتهاء الدالة الخارجية، قم بتعيين قيمته إلى `null` لتحرير الذاكرة.
  • •كن حذراً عند استخدام الـ Closures مع الـ Event Listeners أو الـ Timers. كل دالة داخلية تحتفظ بمرجع للنطاق الخارجي، مما قد يؤدي إلى تسرب في الذاكرة إذا لم تُحرر بشكل صحيح.
  • •استخدم `let` بدلاً من `var` في loops عند إنشاء دوال داخلية. هذا يضمن أن كل دالة تحتفظ بقيمة المتغير في لحظة إنشائها، وليس بقيمته النهائية بعد انتهاء الـ loop.
  • •تعلم كيف تعمل الـ Closures مع الـ `this` في JavaScript. إذا كانت الدالة الداخلية تستخدم `this`، فإنها ستشير إلى `this` الخاص بالدالة الخارجية، وليس إلى الكائن الذي تُستدعى منه. يمكنك حل هذه المشكلة باستخدام `bind` أو حفظ مرجع لـ `this` في متغير:
javascript
const obj = {
 name: 'Object',
 getName: function() {
 const self = this;
 return function() {
 return self.name;
 };
 }
};

const getName = obj.getName();
console.log(getName()); // 'Object'

الـ Closures ليست مجرد أداة لكتابة كود أنيق، بل هي آلية قوية يمكن أن تجعل كودك أكثر مرونة وقوة. لكن مع هذه القوة تأتي مسؤولية فهم كيفية عملها وتأثيرها على الأداء والذاكرة. إذا استخدمت الـ Closures بحكمة، يمكنك بناء تطبيقات سريعة وفعالة. إذا أسأت استخدامها، فقد تواجه مشاكل في الأداء وتسربات في الذاكرة يصعب تتبعها.

خلاصة المهندس: سطرين فقط لتتذكرهما دائماً

الـ Closures هي سلاح ذو حدين: إما تبني بها كوداً ذكياً ومرناً، أو تخلق بها مشاكل أداء لا تُحل بسهولة. القاعدة الذهبية؟ استخدمها عندما تحتاج إلى حفظ حالة بين استدعاءات الدوال، وتجنبها عندما تحتفظ بمراجع لمتغيرات كبيرة دون داعٍ. وإذا وجدت نفسك تكتب دالة داخلية داخل loop، توقف وفكر مرتين: هل هذا هو الحل الأمثل، أم أنني سأخلق تسرباً في الذاكرة؟

closures memory management javascript advanced web development performance

التعليقات

العودة للمقالات
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر