لو هنتكلم عن الmemory management بشكل عام ف لغات البرمجة بتتقسم لشقين ، شق بيخليك إنت كمبرمج تتحكم manualluy ب إيدك وتخصص الmemory و ت free up زي ال c++ وال c عن طريق functions زي malloc و free وغيرهم وتقدر تستخدم libraries تضيف نوع من أنواع ال garbage collection زي boehm-demers-weiser ، بس دي مش جزء من اللغة نفسها ، ولغات تانية بتعمل دا بشكل automated عن طريق الgarbage collection زي الjava و python و js ، فيه شق تالت بقي خرج برا الصندوق وفكرت بشكل مختلف زي Rust ، عملت نظام خاص بيها إسمه الownership بيطبق وقت الcompile time وبيتكون من شوية principles زي الsingle owner وال scope و ال borrowing وال safe references ، تعال بقي نتكلم ف حبيبتنا ،
لو لقيت نفسك في موقف المتصفح يقفل فجأة أو ي freeze
ممكن تتسائل إيه الأسباب اللي ورا ده. ممكن تقول السبب أخطاء في js code، وفعلاً ده صح في أغلب الأوقات. فيه كذا طريقة js ممكن تستهلك بيها limit of a browser’s memory capacity
الحاجات دي بتتسمى memory leaks (تسريبات الذاكرة) وبتأثر بشكل كبير على تجربة المستخدم. واحدة من الطرق الشائعة اللي اللغات البرمجية بتستخدمها لمكافحة الحالة دي هي استخدام garbage collection.
ببساطة، الـ garbage collection هي نوع من automatic memory management . بتسترجع الذاكرة اللي بيشغلها الobjects اللي مبقتش مستخدمة في execution.
فهل جافاسكريبت بتستخدم garbage collection؟
أيوة، جافاسكريبت بتستخدم طريقة لgarbage collection بشكل دوري لما يبقي مفيش references للunused object or variable. العملية دي بتتم بشكل automated وبتساعد في ضمان إن المتصفح ميتحملش overload في الذاكرة ويتوقف.
العملية دي بتمشي كالتالي:
- تخصيص مساحة الذاكرة المطلوبة للبرنامج (Allocation)
- processing و execution للأكواد.
- تحرير مساحة الذاكرة من ال variables و objects اللي مبقتش مستخدمة.
علشان نعرف إيه ال variables و objects اللي محتاجة تتحذف، بيستخدموا اتنين algorithms رئيسيتين اسمهم Mark-and-sweep
.
الalgorithms lifecycle دي بتتكون من مرحلتين
مرحلة التعليم (Mark Phase)
المرحلة دي بت scans ال variables و objects المختلفة وبتتأكد إنها referenced من حاجة تانية في البرنامج. لو مفيش reference، بتحذف الobject من الذاكرة.
مرحلة الكنس (Sweep Phase)
بتنضف ال heap memory من كل الobjects اللي ملهاش reference.
أي object قيمته false بيتشال من ال heap memory.
العملية دي بتحصل بشكل automated لكن ده مش معناه إن ال JavaScript developer مش لازم يقلق منها. العكس صحيح، تجاهلها ممكن يكون ضار بتنفيذ البرنامج، تعالوا نستكشف الموضوع ده أكتر.
إيه هو الـ Memory Leak في الJavaScript ؟
زي ما وضحنا دور الـ garbage collector في منع الmemory leaks، إيه هو الـ memory leak بالتحديد؟
ببساطة، الـ memory leak هو البيانات اللي الcompiler نسي يستخدمها. ممكن نعرفها ك memory مبقتش مطلوبة لتنفيذ برنامجك لكنها محصلهاش released علشان ت free up ال memory capacity.
ليه بيحصل memory leak ؟
لما بتcreate ال objects وvariables، الcompiler بيستهلك بعض من الmemory علشان يتابع المعلومات دي.
الـ garbage collector بيكون عنده القدرة إنه يعرف امتى المتغير مبقاش مطلوب لتنفيذ البرنامج.
ال memory leak بيحصل ازاي؟
الـ memory leak بيحصل لما البرنامج ميعدش محتاج المتغير اللي اتعمل، لكن run time environment (المتصفح) بتعتقد إنك لسه محتاجه. ده بيسبب تعارض بين بيئة التشغيل وتنفيذ الكود. مقارنةً بخطأ typical runtime error، الـ memory leak غالبًا مش بتظهر في شكل خطأ واضح. وده لأنهم مش بينتجوا عن كود مش valid بشكل صريح، لكن من عيب منطقي في الstructure للكود.
النتيجة
في النهاية، ده بيسبب بطء في أداء التطبيق وغالبًا بيوصل لتعطل أو تهنيج المتصفح بالكامل.
بعض السيناريوهات الشائعة اللي بتسبب الـ memory leak في الJavaScript واللي لازم تتجنبها كdeveloper
Infinite Loops
يمكن تكون أشهر سبب لmemory leaks، الinfinite loops بتسبب تعطل المتصفح خلال تفاعل المستخدم. جرب تحط الكود ده في ال developer console debugging tools:
while (true) {
console.log("This will cause a memory leak!");
}
تنفيذ الكود ده هيخلي المتصفح يهنج. لكن الكود ده معمول علشان يسبب تmemory leak ومش العادي إنك تلاقيه في production code . الغالب إن تايمر منسي يخلق السيناريو ده واستخدام setInterval غالبًا ورا الحالات دي. خد التايمر ده كمثال
var counter = 10;
setInterval(function(){
console.log(counter);
counter--;
if (counter === 0) {
console.log("I am running!");
}
}, 1000);
من الوهلة الأولى، ممكن تفترض إن الloop دي هتتوقف بعد 10 مرات. لكن setInterval مش بيعمل غير إنه يمرر function محددة. بنفسه، مفيش stop أو break ! ، والموضوع ده ممكن يتنسى.
لما نستخدم clearInterval، نقدر نوقف التايمر . بياخد بارامتر واحد بيحدد ال function المحددة اللي عايز توقفها.
var counter = 10;
var runningFunction = setInterval(function(){
console.log(counter);
counter--;
if (counter === 0) {
console.log("I am running!");
clearInterval(runningFunction);
}
}, 1000);
إضافة clearInterval لما بنستخدم setInterval ممكن تمنع memory leaks غير الضرورية في كودك.
الـ Closures
زي ما ممكن تكون عارف، الـ closures هي ركن أساسي من أساسيات جافاسكريبت. الـ closures بتتسمى كتير function بترجع function . الفكرة الرئيسية إن ال inner function عندها access لمتغيرات ال outer functions خلينا نبين ده بمثال:
function buildName(name) {
var greeting = "Hello, " + name + "!";
var sayHello = function() {
var welcome = greeting + " Welcome!";
console.log(greeting);
};
return sayHello;
}
في الكود ده، الدالة buildName بتعمل متغير اسمه greeting جوا local scope خاص بيها.
الدالة sayHello موجودة كدالة داخلية (جوا buildName). وده بيخلي sayHello تقدر توصل للمتغير greeting وتستخدمه، حتى لو اتعمل في scope مختلف عن مكانها.
الميزة هنا إن الـ closures بتحتفظ بreferences للمتغيرات حتى بعد ما تخرج من scope الـfunction اللي عملتهم، وده اللي بيخلي closures مفيدة جدًا لما بنكتب كود بيتكرر، أو لو عايزين نخلي بعض القيم ثابتة طول فترة تنفيذ الكود.
إزاي الـ closures ممكن تسبب memory leak ؟
الـ memory leak بيحصل في الـ closure لو واحد من المتغيرات اللي حصلها declared في الouter function بقت متاحة للinner nested function.
ال reference ده لازم يفضل موجود في الذاكرة علشان ال inner nested function توصل للمتغيرات دي. عشان كده، المتغيرات دي مش هتتحذف بالـ garbage collector وده ممكن يسبب memory leak .
الglobal variables الغير مقصودة (Accidental )
من السهل الوقوع في فخ ال memory leak بسبب وجود global variables في الكود. هنا مثال على إنشاء global variables
var createdVariable = "This is a global variable";
function myFunction() {
// المتغير createdVariable متاح هنا جوا myFunction()
}
أي متغير اتعمل في الtop scope level of the document بيبقى global.
في المثال ده، createdVariable اتعمل برا الscope بتاع myFunction، ومن ثم مفيش local scopes تانية، globalVariable بيعيش في الtop level.
الGlobal variables مش بتتحذف بالـ garbage collector. لازم ناخد الحذر دايمًا لما نستخدم الGlobal variables في كودنا.
فكر في window object في المتصفح، ده في الأساس highest tier of scope
اللي الjs بتعيش فيه.
ممكن تجرب ده بفتح ال dev tools وكتابة this
في console.
ال window object بيرجع لنا هنا، بيبين إن current scope هو global scope.
أي متغيرات معلنة بشكل global ممكن تعملها access من كل الscripts والlibraries والfunctions في JavaScript document . وده واحد من الأسباب الرئيسية ليه مش بيتجمع بالـ garbage collector.
ألحل العملي بقي عشان أقلل من ال Global variables
- استخدام
use strict
- تعتمد ع ال Block scope إستخدام let بدل var