Memoization في JavaScript: ازاي توفّر 99.9% من زمن التنفيذ بـ 5 أسطر
لو الكود عندك بيستدعي نفس الدالة بنفس المدخل آلاف المرات، الـ CPU بيعيد نفس الحسبة من الصفر كل مرة. Memoization بتحفظ نتيجة كل استدعاء في Map وبترجّعها فورًا في المرة اللي بعدها. fibonacci(45) بتنزل من 8.4 ثانية إلى 0.9 ملي ثانية على نفس الجهاز.
المشكلة باختصار
الـ recursion في حسابات معينة بيعمل شجرة استدعاءات ضخمة فيها تكرار مرعب. fibonacci(45) الكلاسيكية بتعمل أكثر من 2.2 مليار استدعاء، 99.99% منهم بيحسبوا نفس الأرقام اللي اتحسبت قبل كده. النتيجة: ركز معايا — الـ CPU بيشتغل ساعة عشان يطلعلك رقم اتحسب فعلًا في أول 30 ميلي ثانية.
مثال بسيط قبل ما ندخل في الكود
تخيّل كاشير في سوبر ماركت. كل ما حد يجيب عبوة كولا، الكاشير بيقفل العين ويعد ثمن الكولا من الأول: العبوة 12 جنيه + الضريبة 1.68 = 13.68. لو 200 عميل في اليوم اشتروا كولا، الكاشير عمل نفس العملية 200 مرة. لو الكاشير كتب الرقم 13.68 على ورقة جنب المكنة في أول مرة، الـ 199 عميل اللي بعد كده هيخلصوا في ثواني.
الورقة دي اسمها cache. والاستراتيجية اللي بتقول "احسب مرة واحدة، ارجع نفس النتيجة لو نفس المدخل جه تاني" اسمها Memoization.
التعريف العلمي الدقيق
Memoization تقنية optimization بتعتمد على شرطين أساسيين:
- الدالة لازم تكون pure — يعني نفس المدخل دايمًا بيدّيك نفس المخرج، وبدون side effects (مفيش كتابة في DB أو تعديل متغيّر خارجي).
- المخرج بيتحدد بالكامل من المدخلات — مفيش اعتماد على وقت أو حالة عشوائية.
الفكرة: نخزّن كل (input → output) في Map، وقبل ما الدالة تنفّذ أي حساب نسأل الـ Map: "شفت المدخل ده قبل كده؟". لو آه، رجّع النتيجة المخزّنة. لو لا، احسب وخزّن.
الكود قبل وبعد — fibonacci بـ Node.js 22
الـ fibonacci الساذج (بدون memoization):
// fib_naive.js
function fib(n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
const t0 = performance.now();
const result = fib(45);
const t1 = performance.now();
console.log(`fib(45) = ${result}`);
console.log(`Time: ${(t1 - t0).toFixed(2)} ms`);
// fib(45) = 1134903170
// Time: 8412.36 msنفس الدالة مع Memoization: