[عنوان هذا المقال] http://cuimingda.com/2009/02/speed-up-your-javascript-part-3.html
يعد Recursion أحد الأعداء الذين يبطئون سرعة تشغيل البرامج النصية. سيؤدي الكثير من التكرار إلى جعل المتصفح أبطأ فأبطأ حتى يتوقف أو يخرج تلقائيًا بشكل مفاجئ وغير مفهوم، لذلك يجب علينا حل هذه السلسلة من مشكلات الأداء في JavaScript. في المقالة الثانية من هذه السلسلة، قدمت بإيجاز كيفية استخدام تقنية الحفظ لاستبدال الكثير من الاستدعاءات المتكررة في الوظائف. الحفظ هو أسلوب يقوم بتخزين نتائج الحسابات السابقة مؤقتًا حتى لا نحتاج إلى إعادة حساب تلك النتائج التي تم حسابها بالفعل. بالنسبة للوظائف التي تقوم بإجراء العمليات الحسابية من خلال العودية، فإن الحفظ هو ببساطة مفيد للغاية. تم كتابة المذكرة التي أستخدمها حاليًا بواسطة Crockford وهي تُستخدم بشكل أساسي في العمليات العودية التي تُرجع الأعداد الصحيحة. بالطبع، لا تقوم جميع الدوال العودية بإرجاع أعداد صحيحة، لذلك نحتاج إلى وظيفة memoizer() أكثر عمومية للتعامل مع المزيد من أنواع الوظائف العودية.
وظيفة المذكرة(الأساسية, ذاكرة التخزين المؤقت){ ذاكرة التخزين المؤقت = ذاكرة التخزين المؤقت ||. {} var Shell = function(arg){ if (!(arg inذاكرة التخزين المؤقت)){ ذاكرة التخزين المؤقت[arg] = الأساسية(shell, arg) } إرجاع ذاكرة التخزين المؤقت[arg] }; return shell;} يختلف هذا الإصدار من الوظيفة قليلاً عن الإصدار الذي كتبه كروكفورد. أولاً، يتم عكس ترتيب المعلمات، ويتم تعيين الوظيفة الأصلية كمعلمة أولى، والمعلمة الثانية هي كائن ذاكرة التخزين المؤقت، وهو أمر اختياري لأنه لا تحتوي جميع الوظائف العودية على معلومات أولية. داخل الوظيفة، أقوم بنقل نوع كائن ذاكرة التخزين المؤقت من مصفوفة إلى كائن حتى يتمكن هذا الإصدار من استيعاب الوظائف العودية التي لا تُرجع أعدادًا صحيحة. في وظيفة الصدفة، أستخدم عامل التشغيل in لتحديد ما إذا كانت المعلمات مضمنة بالفعل في ذاكرة التخزين المؤقت. تعتبر طريقة الكتابة هذه أكثر أمانًا من اختبار النوع غير المحدد، لأن القيمة غير المحددة هي قيمة إرجاع صالحة. مازلنا نستخدم تسلسل فيبوناتشي المذكور سابقًا للتوضيح:
var fibonacci = memoizer(function (recur, n) { return recur(n - 1) + recur(n - 2); }, {"0": 0, "1" :1}); وبالمثل، فإن تنفيذ الدالة fibonacci(40) سوف يستدعي الدالة الأصلية 40 مرة فقط، بدلاً من 331,160,280 مرة المبالغ فيها. يعد الحفظ أمرًا رائعًا للخوارزميات العودية ذات مجموعات النتائج المحددة بدقة. ومع ذلك، هناك بالفعل العديد من الخوارزميات العودية التي ليست مناسبة للتحسين باستخدام طرق الحفظ.
أصر أحد أساتذتي في المدرسة دائمًا على أن أي موقف يتم فيه استخدام التكرار يمكن استبداله بالتكرار إذا لزم الأمر. في الواقع، غالبًا ما يتم استخدام التكرار والتكرار كطرق للتعويض عن بعضها البعض، خاصة عندما تسوء الطريقة الأخرى. إن تقنية تحويل الخوارزميات العودية إلى خوارزميات تكرارية مستقلة أيضًا عن لغة التطوير. ومع ذلك، فإن أهمية JavaScript أكبر، لأن موارد بيئة التنفيذ مقيدة للغاية. دعونا نراجع خوارزمية عودية نموذجية، مثل فرز الدمج. يتطلب تنفيذ هذه الخوارزمية في JavaScript الكود التالي:
function merge(left, right){ var result = []; while (left.length > 0 && right.length > 0) { if (left[0] < right[0]){ result.push(left.shift()); } else { result.push(right.shift()); } } return result.concat(left ).concat (يمين)؛}// دمج خوارزمية الفرز function mergeSort(items){ if (items.length == 1) { return items } var middle = Math.floor(items.length / 2) باستخدام العودية، left = items. Slice(0, middle), right = items.slice(middle); return merge(mergeSort(left), mergeSort(right));} قم باستدعاء الدالة mergeSort() لمعالجة مصفوفة، ويمكنك إرجاع المصفوفة التي تم فرزها. لاحظ أنه في كل مرة يتم فيها استدعاء الدالة mergeSort()، سيكون هناك مكالمتين متكررتين. لا يمكن تحسين هذه الخوارزمية باستخدام الحفظ، لأنه يتم حساب كل نتيجة واستخدامها مرة واحدة فقط، وحتى تخزين النتائج مؤقتًا لا فائدة منه. إذا كنت تستخدم الدالة mergeSort() على مصفوفة مكونة من 100 عنصر، فسيكون هناك إجمالي 199 استدعاء. ستقوم مجموعة مكونة من 1000 عنصر بإجراء 1999 مكالمة. في هذه الحالة، الحل الذي نقدمه هو تحويل الخوارزمية العودية إلى خوارزمية تكرارية، مما يعني تقديم بعض الحلقات (بالنسبة للخوارزمية، يمكنك الرجوع إلى هذه المقالة "معالجة القائمة: فرز مرة أخرى، بشكل طبيعي"):
// استخدم التنفيذ التكراري وظيفة خوارزمية فرز الدمج mergeSort(items){ if (items.length == 1) { return items; } varwork = []; for (var i=0, len=items.length; i < len; i++){ العمل .push([items[i]]); }work.push([]); // في حالة وجود عدد فردي من العناصر for (var lim=len; lim > 1; lim = (lim+1)/2 ) { for (var j=0,k=0; k < lim; j++, k+=2){ العمل[j] = دمج(work[k], العمل[k+1]); } العمل[j] = [ ]; // في حالة وجود عدد فردي من العناصر } return العمل[0];}يستخدم تطبيق خوارزمية الفرز المدمج سلسلة من الحلقات بدلاً من العودية للفرز. نظرًا لأن فرز الدمج يقوم أولاً بتقسيم المصفوفة إلى عدة صفائف بعنصر واحد فقط، فإن هذه الطريقة تنفذ هذه العملية بشكل أكثر وضوحًا بدلاً من استخدام دالة متكررة بشكل ضمني. تتم تهيئة مصفوفة العمل لتحتوي على مصفوفة من المصفوفات ذات العنصر الواحد. في كل مرة في الحلقة، يتم دمج الصفيفين ويتم إعادة النتيجة المدمجة إلى صفيف العمل. عند تنفيذ الوظيفة، سيتم إرجاع النتيجة التي تم فرزها من خلال العنصر الأول في مصفوفة العمل. في هذا التنفيذ لفرز الدمج، يتم تنفيذ الخوارزمية أيضًا دون استخدام أي تكرار. ومع ذلك، فإن هذا يقدم عددًا كبيرًا من الحلقات، ويعتمد عدد الحلقات على عدد العناصر في المصفوفة المراد فرزها، لذلك قد نحتاج إلى تعديلها باستخدام التقنيات التي تمت مناقشتها في المقالة السابقة للتعامل مع هذا الحمل الإضافي .
لتلخيص المبادئ الأساسية، يجب عليك توخي الحذر عند استخدام التكرار. يعد الحفظ والتكرار حلين لاستبدال التكرار. والنتيجة الأكثر مباشرة هي بالطبع تجنب مربع الحوار الذي يطالب البرنامج النصي بالخروج عن نطاق السيطرة.