كيفية استخدام 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لتذكر (تخزين) النتائج. الآن بعد أن أصبح لديك فهم أساسي لما نحاول تحقيقه ، إليك تعريف رسمي:

Memoization هي تقنية تحسين تستخدم بشكل أساسي لتسريع برامج الكمبيوتر عن طريق تخزين نتائج مكالمات الوظائف باهظة الثمن وإرجاع النتيجة المخزنة مؤقتًا عند حدوث نفس المدخلات مرة أخرى

الحفظ بعبارات بسيطة يعني الحفظ أو التخزين في الذاكرة. عادةً ما تكون الوظيفة المحفوظة في الذاكرة أسرع لأنه إذا تم استدعاء الوظيفة لاحقًا بالقيمة (القيم) السابقة ، فبدلاً من تنفيذ الوظيفة ، سنقوم بإحضار النتيجة من ذاكرة التخزين المؤقت.

إليك ما قد تبدو عليه الوظيفة البسيطة التي تم حفظها في الذاكرة (وإليك 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 :)

يمكنك متابعتي على تويتر للحصول على آخر التحديثات. لقد بدأت أيضًا في نشر المزيد من المشاركات الحديثة على مدونتي الشخصية.