كيفية استخدام Memoize لتخزين نتائج وظيفة JavaScript مؤقتًا وتسريع التعليمات البرمجية الخاصة بك
الوظائف جزء لا يتجزأ من البرمجة. أنها تساعد على إضافة نمطية و إعادة استخدام إلى رمز لدينا.
من الشائع جدًا تقسيم برنامجنا إلى أجزاء باستخدام وظائف يمكننا استدعاؤها لاحقًا لأداء بعض الإجراءات المفيدة.
في بعض الأحيان ، يمكن أن يصبح استدعاء دالة ما مكلفًا عدة مرات (على سبيل المثال ، وظيفة لحساب معامل الرقم). ولكن هناك طريقة يمكننا من خلالها تحسين هذه الوظائف وجعلها تنفذ بشكل أسرع: التخزين المؤقت .
على سبيل المثال ، لنفترض أن لدينا a function
لإرجاع مضروب الرقم:
function factorial(n) { // Calculations: n * (n-1) * (n-2) * ... (2) * (1) return factorial }
عظيم ، فلنجد الآن factorial(50)
. سيقوم الكمبيوتر بإجراء العمليات الحسابية ويعيد لنا الإجابة النهائية ، حلوة!
عندما يتم ذلك ، دعنا نجد factorial(51)
. يقوم الكمبيوتر مرة أخرى بإجراء عدد من العمليات الحسابية ويعطينا النتيجة ، لكن ربما تكون قد لاحظت أننا نكرر بالفعل عددًا من الخطوات التي كان من الممكن تجنبها. الطريقة المثلى ستكون:
factorial(51) = factorial(50) * 51
لكننا نقوم function
بالحسابات من الصفر في كل مرة يتم استدعاؤها:
factorial(51) = 51 * 50 * 49 * ... * 2 * 1
ألن يكون رائعًا إذا factorial
تمكنت وظيفتنا بطريقة ما من تذكر القيم من حساباتها السابقة واستخدامها لتسريع التنفيذ؟
يأتي التذكر ، وسيلة function
لتذكر (تخزين) النتائج. الآن بعد أن أصبح لديك فهم أساسي لما نحاول تحقيقه ، إليك تعريف رسمي:
الحفظ بعبارات بسيطة يعني الحفظ أو التخزين في الذاكرة. عادةً ما تكون الوظيفة المحفوظة في الذاكرة أسرع لأنه إذا تم استدعاء الوظيفة لاحقًا بالقيمة (القيم) السابقة ، فبدلاً من تنفيذ الوظيفة ، سنقوم بإحضار النتيجة من ذاكرة التخزين المؤقت.
إليك ما قد تبدو عليه الوظيفة البسيطة التي تم حفظها في الذاكرة (وإليك CodePen في حالة رغبتك في التفاعل معها) :
// a simple function to add something const add = (n) => (n + 10); add(9); // a simple memoized function to add something const memoizedAdd = () => { let cache = {}; return (n) => { if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = n + 10; cache[n] = result; return result; } } } // returned function from memoizedAdd const newAdd = memoizedAdd(); console.log(newAdd(9)); // calculated console.log(newAdd(9)); // cached
الوجبات الجاهزة Memoization
بعض الوجبات السريعة من الكود أعلاه هي:
memoizedAdd
إرجاعfunction
الذي يتم استدعاؤه لاحقًا. هذا ممكن لأنه في JavaScript ، الوظائف هي كائنات من الدرجة الأولى تتيح لنا استخدامها كوظائف ذات ترتيب أعلى وإرجاع وظيفة أخرى.cache
يمكن أن تتذكر قيمها لأن الوظيفة المرتجعة لها إغلاق فوقها.- من الضروري أن تكون وظيفة الذاكرة نقية. ستعيد الدالة الصرفة نفس المخرجات لمدخل معين بغض النظر عن عدد مرات استدعائها ، مما يجعل
cache
العمل كما هو متوقع.
كتابة memoize
الوظيفة الخاصة بك
يعمل الكود السابق بشكل جيد ولكن ماذا لو أردنا تحويل أي وظيفة إلى وظيفة ذاكرة؟
إليك كيفية كتابة دالة memoize الخاصة بك (codepen):
// a simple pure function to get a value adding 10 const add = (n) => (n + 10); console.log('Simple call', add(3)); // a simple memoize function that takes in a function // and returns a memoized function const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; // just taking one argument here if (n in cache) { console.log('Fetching from cache'); return cache[n]; } else { console.log('Calculating result'); let result = fn(n); cache[n] = result; return result; } } } // creating a memoized function for the 'add' pure function const memoizedAdd = memoize(add); console.log(memoizedAdd(3)); // calculated console.log(memoizedAdd(3)); // cached console.log(memoizedAdd(4)); // calculated console.log(memoizedAdd(4)); // cached
الآن هذا رائع! memoize
ستعمل هذه الوظيفة البسيطة على التفاف أي شيء بسيط function
في مكافئ مذكّر. يعمل الكود بشكل جيد للوظائف البسيطة ويمكن تعديله بسهولة للتعامل مع أي عدد arguments
حسب احتياجاتك. بديل آخر هو الاستفادة من بعض مكتبات الأمر الواقع مثل:
- لوداش
_.memoize(func, [resolver])
- ES7
@memoize
ديكور من decko
Memoizing الوظائف العودية
إذا حاولت تمرير دالة تكرارية إلى memoize
الوظيفة أعلاه أو _.memoize
من Lodash ، فلن تكون النتائج كما هو متوقع نظرًا لأن الوظيفة العودية في مكالماتها اللاحقة ستنتهي في النهاية باستدعاء نفسها بدلاً من الوظيفة المذكرة وبالتالي عدم الاستفادة من cache
.
فقط تأكد من أن وظيفتك العودية تستدعي وظيفة الذاكرة. إليك كيف يمكنك تعديل مثال كتابي عاملي (codepen):
// same memoize function from before const memoize = (fn) => { let cache = {}; return (...args) => { let n = args[0]; if (n in cache) { console.log('Fetching from cache', n); return cache[n]; } else { console.log('Calculating result', n); let result = fn(n); cache[n] = result; return result; } } } const factorial = memoize( (x) => { if (x === 0) { return 1; } else { return x * factorial(x - 1); } } ); console.log(factorial(5)); // calculated console.log(factorial(6)); // calculated for 6 and cached for 5
بعض النقاط التي يجب ملاحظتها من هذا الرمز:
- و
factorial
ظيفة تدعو متكرر نسخة memoized في حد ذاته. - تعمل وظيفة الذاكرة المؤقتة على تخزين قيم العوامل السابقة بشكل مؤقت مما يحسن الحسابات بشكل كبير حيث يمكن إعادة استخدامها
factorial(6) = 6 * factorial(5)
هل الحفظ هو نفسه التخزين المؤقت؟
نعم نوعا ما. Memoization هو في الواقع نوع معين من التخزين المؤقت. بينما يمكن أن يشير التخزين المؤقت بشكل عام إلى أي تقنية تخزين (مثل التخزين المؤقت لـ HTTP) للاستخدام المستقبلي ، فإن الحفظ يتضمن على وجه التحديد التخزين المؤقت لقيم الإرجاع لـ function
.
متى تحفظ وظائفك
على الرغم من أنه قد يبدو أنه يمكن استخدام الحفظ مع جميع الوظائف ، إلا أنه في الواقع له حالات استخدام محدودة:
- من أجل حفظ دالة ، يجب أن تكون نقية بحيث تكون القيم المعادة هي نفسها لنفس المدخلات في كل مرة
- Memoizing عبارة عن مقايضة بين المساحة المضافة والسرعة المضافة ، وبالتالي فهي مهمة فقط للوظائف التي لها نطاق إدخال محدود بحيث يمكن الاستفادة من القيم المخزنة مؤقتًا بشكل متكرر
- قد يبدو أنه يجب عليك حفظ مكالمات واجهة برمجة التطبيقات (API) الخاصة بك ، ولكن هذا ليس ضروريًا لأن المتصفح يخزنها تلقائيًا مؤقتًا نيابة عنك. راجع التخزين المؤقت HTTP لمزيد من التفاصيل
- أفضل حالة استخدام وجدتها للوظائف المذكرة هي الوظائف الحسابية الثقيلة التي يمكن أن تحسن الأداء بشكل كبير (العوامل والفيبوناتشي ليست أمثلة جيدة حقًا في العالم الحقيقي)
- إذا كنت تستخدم React / Redux ، فيمكنك التحقق من إعادة التحديد التي تستخدم محددًا مذكّرًا للتأكد من أن الحسابات تحدث فقط عند حدوث تغيير في جزء ذي صلة من شجرة الحالة.
قراءة متعمقة
يمكن أن تكون الروابط التالية مفيدة إذا كنت ترغب في معرفة المزيد عن بعض الموضوعات من هذه المقالة بمزيد من التفصيل:
- وظائف ذات ترتيب أعلى في JavaScript
- الإغلاق في JavaScript
- وظائف نقية
_.memoize
مستندات لوداش ورمز المصدر- المزيد من أمثلة الحفظ هنا وهنا
- رد فعل / إعادة تحديد
آمل أن تكون هذه المقالة مفيدة لك ، وقد اكتسبت فهمًا أفضل للتذكير في JavaScript :)
يمكنك متابعتي على تويتر للحصول على آخر التحديثات. لقد بدأت أيضًا في نشر المزيد من المشاركات الحديثة على مدونتي الشخصية.