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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر

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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر

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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر

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

Closures في JavaScript: عندما تحتفظ الدوال بذكرياتها السرية

اكتشف كيف تعمل الـ Closures خلف الكواليس في JavaScript، لماذا هي أقوى من مجرد دوال عادية، وكيف تحل مشاكل حقيقية في الكود مثل الـ Memory Leaks والـ Event Handlers دون أن تدري. أمثلة واقعية من شركات مثل فيسبوك وجوجل.

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

تخيل أنك تكتب كوداً بسيطاً لدالة counter تزيد رقماً كل مرة تُستدعى فيها. بعد ساعتين من الديباج، تكتشف أن الرقم يعود للصفر في كل استدعاء. المشكلة ليست في المنطق، بل في شيء أعمق: الدوال في JavaScript لا تتذكر شيئاً بين الاستدعاءات. هنا تأتي الـ Closures لتنقذك، لكنها تأتي أيضاً بمفاجآت غير متوقعة مثل تسريبات الذاكرة والـ Bugs التي تظهر بعد أيام من الانتشار. لنبدأ من حيث انتهى الآخرون: كيف تعمل هذه الآلية بالضبط؟

في عام ٢٠١٨، واجه فريق فيسبوك مشكلة غريبة: بعض الـ Event Listeners في تطبيق موبايلهم كانت تستهلك ذاكرة بشكل مفرط حتى بعد إزالة العنصر من الـ DOM. السبب؟ الـ Closures التي احتفظت بمراجع لعناصر لم تعد موجودة، لكن الـ Garbage Collector لم يستطع مسحها لأن الدوال الداخلية ما زالت تحتفظ بها. هذه ليست قصة درامية، بل واقع يومي للمطورين الذين لا يفهمون كيف تتعامل JavaScript مع الذاكرة. لنفكك الموضوع من الصفر.

ما هي الـ Closure حقاً؟ ليس مجرد دالة داخل دالة

الكثير من المقالات تشرح الـ Closure بأنها "دالة داخل دالة"، وهذا صحيح جزئياً لكنه مضلل. الـ Closure ليست مجرد تركيب لغوي، بل هي آلية تعمل خلف الكواليس في محرك JavaScript (مثل V8 في كروم). عندما تُعرّف دالة داخل دالة أخرى، الدالة الداخلية تحصل على وصول إلى متغيرات الدالة الخارجية حتى بعد انتهاء تنفيذ الدالة الخارجية. لكن كيف يحدث هذا بالضبط؟

في معظم لغات البرمجة، عندما تنتهي دالة من التنفيذ، يتم مسح جميع متغيراتها المحلية من الذاكرة. لكن في JavaScript، إذا كانت هناك دالة داخلية ما زالت قيد الاستخدام (مثلاً، مررت كcallback إلى setTimeout)، فإن محرك JavaScript يحتفظ بمرجع لجميع المتغيرات التي تحتاجها هذه الدالة الداخلية. هذا المرجع يُسمى Lexical Environment، وهو ليس مجرد كائن عادي، بل هو جزء من سلسلة الـ Scopes التي تتبعها JavaScript عند البحث عن المتغيرات.

javascript
// مثال بسيط لكن عميق
function outer() {
 let count = 0;
 return function inner() {
 count++;
 return count;
 };
}

const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
// حتى بعد انتهاء outer، inner ما زالت تتذكر count
// لأن الـ Lexical Environment لـ outer لم يُمسح بعد

لاحظ هنا أن outer انتهت من التنفيذ منذ فترة طويلة، لكن inner ما زالت تستطيع الوصول إلى count. هذا ليس سحراً، بل هو نتيجة لكيفية تعامل محرك JavaScript مع الذاكرة. الـ Lexical Environment لـ outer يُحتفظ به في الذاكرة طالما أن هناك دالة داخلية ما زالت تشير إليه. هذه هي الـ Closure في أبسط صورها، لكنها تصبح أكثر تعقيداً عندما ندخل في الـ Loops والـ Event Handlers.

الـ Closures في الـ Loops: الفخ الذي يقع فيه الجميع

إذا سألت أي مطور جافاسكريبت عن أسوأ كابوس واجهه مع الـ Closures، ستجد أن ٩٠٪ منهم سيذكرون الـ Loops. المشكلة ليست في الـ Closures نفسها، بل في كيفية تعاملها مع المتغيرات التي تُعرّف باستخدام var. لنأخذ مثالاً كلاسيكياً:

javascript
for (var i = 0; i < 3; i++) {
 setTimeout(function() {
 console.log(i); // ماذا تتوقع أن يطبع؟
 }, 1000);
}
// النتيجة: 3, 3, 3
// ليس 0, 1, 2 كما قد يتوقع البعض

لماذا يحدث هذا؟ لأن var تُعرّف متغيراً واحداً فقط لـ i في نطاق الدالة (أو النطاق العام)، وعندما يتم تنفيذ الـ setTimeout بعد انتهاء الـ Loop، قيمة i تكون ٣ بالفعل. الـ Closure هنا تحتفظ بمرجع لـ i، وليس بقيمة i في لحظة إنشاء الدالة. هذا السلوك يسبب مشاكل كبيرة في الـ Event Handlers، حيث قد ينتهي بك الأمر بأن جميع الأزرار في قائمة تطبع نفس القيمة بدلاً من القيمة الخاصة بكل زر.

الحل؟ استخدم let بدلاً من var. let تُعرّف متغيراً جديداً في كل تكرار من الـ Loop، مما يعني أن كل closure ستحتفظ بقيمة مختلفة لـ i. لكن حتى هذا الحل ليس مثالياً إذا لم تفهم كيف تعمل الـ Block Scopes في ES6. لنرى المثال المصحح:

javascript
for (let i = 0; i < 3; i++) {
 setTimeout(function() {
 console.log(i); // الآن يطبع: 0, 1, 2
 }, 1000);
}
// لأن let تُعرّف متغيراً جديداً في كل تكرار
// وكل closure تحتفظ بقيمة مختلفة لـ i

الـ Closures والـ Memory Leaks: عندما تحتفظ الدوال بأكثر مما تحتاج

في عام ٢٠١٩، واجه فريق جوجل مشكلة في تطبيق Google Docs: بعض المستخدمين كانوا يعانون من بطء شديد بعد ساعات من التحرير المستمر. بعد تحليل عميق، اكتشف الفريق أن بعض الـ Event Listeners التي تُضاف ديناميكياً كانت تحتفظ بمراجع لعناصر DOM ضخمة حتى بعد إزالتها من الصفحة. السبب؟ الـ Closures التي احتفظت بهذه المراجع دون داعٍ.

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

javascript
function setupButton(buttonId) {
 const button = document.getElementById(buttonId);
 const largeData = new Array(1000000).fill('data'); // بيانات ضخمة
 
 button.addEventListener('click', function() {
 console.log('Button clicked');
 // الدالة الداخلية لا تستخدم largeData أبداً
 // لكنها تحتفظ بمرجع لها بسبب الـ Closure
 });
}

setupButton('myButton');
// حتى بعد إزالة الـ button من DOM، largeData لن تُمسح
// لأن الـ Event Listener ما زال موجوداً

في هذا المثال، الـ Closure تحتفظ بمرجع لـ largeData رغم أن الدالة الداخلية لا تستخدمها أبداً. هذا يسبب تسريب ذاكرة لأن الـ Garbage Collector لا يستطيع مسح largeData طالما أن هناك دالة ما زالت تشير إليها. الحل؟ إما أن تتجنب تعريف المتغيرات غير الضرورية في نطاق الدالة الخارجية، أو تستخدم WeakMap لتخزين البيانات التي قد تُسبب تسريبات.

الـ Closures في الـ Module Pattern: كيف تبني واجهات برمجية نظيفة

أحد أقوى استخدامات الـ Closures هو بناء الـ Modules في JavaScript. قبل ظهور ES6 Modules، كان المطورون يستخدمون الـ Closures لإنشاء واجهات برمجية نظيفة تخفي التفاصيل الداخلية. هذا النمط يُسمى Module Pattern، وهو ما زال مستخدماً حتى اليوم في بعض المكتبات الكبيرة مثل jQuery.

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

javascript
const counterModule = (function() {
 let count = 0; // خاص ولا يمكن الوصول إليه من الخارج
 
 function increment() {
 count++;
 return count;
 }
 
 function decrement() {
 count--;
 return count;
 }
 
 function getCount() {
 return count;
 }
 
 // الـ Public API
 return {
 increment,
 decrement,
 getCount
 };
})();

console.log(counterModule.increment()); // 1
console.log(counterModule.increment()); // 2
console.log(counterModule.getCount()); // 2
// لا يمكنك الوصول إلى count مباشرة لأن الـ Closure تخفيه

هذا النمط مفيد جداً لبناء مكتبات أو وحدات برمجية معقدة دون تلويث النطاق العام. الـ Closures هنا تعمل كآلية للتغليف (Encapsulation)، وهي مفهوم أساسي في البرمجة الكائنية. لاحظ كيف أن count لا يمكن الوصول إليه من خارج الوحدة، بينما الدوال التي نريد تعريضها متاحة عبر الكائن الذي أرجعناه.

الـ Closures والـ Currying: عندما تصبح الدوال أكثر مرونة

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

لنأخذ مثالاً عملياً: تخيل أنك تبني مكتبة لإرسال طلبات HTTP، وتريد أن تسمح للمستخدمين بتكوين الـ Base URL مرة واحدة ثم استخدامه في جميع الطلبات. بدلاً من أن تطلب من المستخدم تمرير الـ Base URL في كل مرة، يمكنك استخدام الـ Currying لإنشاء دالة مهيأة مسبقاً:

javascript
function createRequest(baseUrl) {
 return function(endpoint, options) {
 const url = `${baseUrl}${endpoint}`;
 return fetch(url, options);
 };
}

const api = createRequest('https://api.example.com');
// الآن يمكنك استخدام api في أي مكان دون تكرار الـ Base URL
api('/users', { method: 'GET' })
 .then(resp> response.json())
 .then(data => console.log(data));

// نفس الشيء مع قاعدة بيانات أخرى
const localApi = createRequest('http://localhost:3000');
localApi('/posts', { method: 'POST', body: JSON.stringify({ title: 'Hello' }) });

هذا المثال يظهر قوة الـ Closures في بناء واجهات برمجية مرنة. الدالة createRequest تحتفظ بقيمة baseUrl في الـ Closure الخاصة بها، ثم تُرجع دالة جديدة تستخدم هذه القيمة عند الاستدعاء. هذا النمط شائع جداً في المكتبات التي تحتاج إلى تهيئة مسبقة، مثل axios وRedux Thunk.

الـ Closures والـ Performance: متى تصبح عبئاً؟

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

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

javascript
function createGameObject(x, y) {
 const position = { x, y };
 const velocity = { x: Math.random(), y: Math.random() };
 const size = 10 + Math.random() * 20;
 
 return {
 update: function() {
 position.x += velocity.x;
 position.y += velocity.y;
 // رسم الكائن على Canvas
 }
 };
}

const objects = Array(1000).fill().map(() => createGameObject(0, 0));
// الآن لدينا 1000 closure تحتفظ بمراجع لـ position, velocity, size
// حتى لو لم نعد نحتاج لبعض هذه الكائنات

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

javascript
function GameObject(x, y) {
 this.position = { x, y };
 this.velocity = { x: Math.random(), y: Math.random() };
 this.size = 10 + Math.random() * 20;
}

GameObject.prototype.update = function() {
 this.position.x += this.velocity.x;
 this.position.y += this.velocity.y;
 // رسم الكائن على Canvas
};

const objects = Array(1000).fill().map(() => new GameObject(0, 0));
// الآن update مشتركة بين جميع الكائنات
// ولا تحتفظ بمراجع غير ضرورية

خلاصة المهندس: نصائح لا تُنسى للعمل مع الـ Closures

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

  • •استخدم let بدلاً من var في الـ Loops لتجنب مشاكل الـ Closures غير المتوقعة.
  • •تجنب تعريف متغيرات ضخمة في نطاق الدوال الخارجية إذا كانت الدوال الداخلية لا تحتاجها، فهذا يسبب تسريبات ذاكرة.
  • •استخدم الـ Module Pattern لبناء واجهات برمجية نظيفة تخفي التفاصيل الداخلية.
  • •فكر مرتين قبل استخدام الـ Closures في الـ Loops أو الدوال التي تُستدعى بشكل متكرر، فقد تؤثر على الأداء.
  • •إذا كنت تبني مكتبة أو وحدة برمجية، استخدم الـ Closures لإنشاء واجهات مرنة مع الحفاظ على الخصوصية.
  • •تذكر دائماً أن الـ Closures تحتفظ بمراجع وليس بقيم، وهذا يعني أنها قد تمنع الـ Garbage Collector من مسح البيانات القديمة.

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

closures javascript web development memory management programming patterns

التعليقات

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

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

المنصة

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

الحساب

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

روابط

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

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

صُنع بـ في مصر