مستوى المقال: متوسط — مفيد لو عندك خبرة JavaScript أساسية وفاهم الـ recursion والـ pure functions.
Memoization في JavaScript: خلّي الدالة تفتكر بدل ما تحسب
لو دالة عندك بتاخد نفس الـ input عشرات المرات وبتحسب نفس النتيجة من الأول كل مرة، إنت بتدفع زمن CPU بدون داعي. Memoization بـ 8 سطور JavaScript بتنزّل دالة Fibonacci من 11,843 مللي ثانية على n=45 لـ 0.4 مللي ثانية على نفس المنطق، بفقد بسيط في الذاكرة.
المشكلة باختصار
الدوال البحتة (pure functions) مع نفس الـ input بترجّع نفس الـ output دايمًا. فلو الدالة دي اتنادت بنفس الـ argument 50 مرة، الـ CPU عمل نفس الشغل 50 مرة وكل مرة فيهم راحت في الهوا. Memoization بتخزّن النتيجة بعد أول استدعاء، وكل استدعاء بعد كده بيرجّع من الذاكرة بدل الحساب — تحسّن ممكن يوصل لآلاف الأضعاف على دوال recursive.
مثال للمبتدئ: المحاسب اللي بيحسب نفس الفاتورة كل يوم
تخيّل محاسب في شركة، كل يوم بيحسب فاتورة العميل أحمد من الصفر. بيعدّ عدد الكراتين، يضرب في السعر، يحسب الضريبة، يطبع الفاتورة. لو العميل نفسه طلب نسخة من نفس الفاتورة 30 مرة في اليوم، المحاسب الغبي هيعيد كل خطوة 30 مرة. المحاسب الذكي هيكتب النتيجة في ورقة جنب مكتبه أول مرة، فلو حد سأل عن نفس الفاتورة، بيرجعها فورًا من الورقة بدل ما يفتح الحسابات تاني.
الورقة دي اسمها cache. وفعل المحاسب الذكي ده بالظبط هو Memoization.
التعريف العلمي للـ Memoization
Memoization تقنية optimization بتحسّن أداء الدوال البحتة بحفظ نتائج الاستدعاءات السابقة في cache (عادةً Map أو object)، وعند الاستدعاء التالي بنفس الـ arguments بترجّع النتيجة من الـ cache مباشرة بدون إعادة الحساب. المصطلح صاغه العالم البريطاني Donald Michie سنة 1968 في ورقة بعنوان "Memo Functions and Machine Learning" نُشرت في Nature.
الشرط الأساسي اللي لازم يتوفر في الدالة: تكون deterministic — يعني نفس الـ input يطلع نفس الـ output في كل الظروف، ومفيش side effects. Memoization على دالة بتقرأ من DB أو بتنادي Math.random() هتكسر منطقك بصمت.
المثال التنفيذي: Fibonacci بدون وبـ Memoization
الـ Fibonacci recursive الكلاسيكي تعقيده O(2^n) — على n=40 بياخد ثواني، على n=45 بياخد عشرات الثواني. السبب إن كل استدعاء بيكرّر شغل سابق ملايين المرات:
// بدون memoization — O(2^n)
function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
console.time('fib45');
console.log(fib(45)); // 1134903170
console.timeEnd('fib45');
// fib45: 11843ms (تقريبًا 12 ثانية على Node 22 / M2)
الدالة دي بتنادي نفسها 1,836,311,903 مرة لحساب fib(45). معظم الاستدعاءات بتعيد حساب قيم اتحسبت قبل كده.