تحسين أداء تطبيق HTML5

مقدمة

يمنحنا HTML5 أدوات رائعة لتحسين المظهر المرئي لتطبيقات الويب. هذا صحيح بشكل خاص في عالم الرسوم المتحركة. ومع ذلك، تظهر تحديات جديدة أيضًا مع هذه القوة الجديدة. في الواقع، هذه التحديات ليست جديدة فحسب، وقد يكون من المنطقي أن تسأل زميلتها التي تعمل في مكتبك، وهي مبرمجة فلاش، عن كيفية التغلب على هذه الأمور في الماضي.

على أي حال، عند العمل على الرسوم المتحركة، من المهم للغاية أن يدرك المستخدمون هذه الرسوم المتحركة على أنها سلسة. ما يجب أن ندركه هو أنّه لا يمكن إنشاء صور متحرّكة بسلاسة من خلال زيادة عدد اللقطات في الثانية الواحدة عن حدّ معيّن. عقلنا، للأسف، أذكى من ذلك. وما ستتعلمه هو أن 30 إطارًا حقيقيًا من الصور المتحركة في الثانية (fps) أفضل بكثير من 60 لقطة في الثانية مع إسقاط عدد قليل من اللقطات في المنتصف. الناس يكرهون الخجل.

ستحاول هذه المقالة منحك الأدوات والتقنيات اللازمة لتحسين تجربة تطبيقك.

الاستراتيجية

ولا نريد بأي حال من الأحوال أن نثنيك عن إنشاء تطبيقات مرئية رائعة ومذهلة باستخدام HTML5.

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

دقة مرئية ++ باستخدام HTML5

تسريع الأجهزة

يُعدّ تسريع الأجهزة معلمًا رئيسيًا مهمًا لأداء العرض بشكل عام في المتصفّح. يتمثّل المخطط العام في نقل المهام التي قد يتم احتسابها بشكل آخر من خلال وحدة المعالجة المركزية (CPU) الرئيسية إلى وحدة معالجة الرسومات (GPU) في محوِّل الرسومات بجهاز الكمبيوتر. ويمكن أن يؤدي ذلك إلى تحقيق مكاسب كبيرة في الأداء ويمكن أيضًا تقليل استهلاك الموارد على الأجهزة الجوّالة.

يمكن تسريع هذه الجوانب من المستند بواسطة وحدة معالجة الرسومات

  • تركيب التخطيط العام
  • عمليات نقل CSS3
  • التحويلات ثلاثية الأبعاد لـ CSS3
  • رسم على اللوحة
  • رسم WebGL ثلاثي الأبعاد

وعلى الرغم من أن تسريع لوحة الرسم وWebGL هي ميزات ذات أغراض خاصة قد لا تنطبق على تطبيقك المحدد، يمكن أن تساعد الجوانب الثلاثة الأولى في جعل جميع التطبيقات أسرع إلى حد كبير.

ما الذي يمكن تسريعه؟

تعمل ميزة تسريع وحدة معالجة الرسومات من خلال تفريغ مهام محدَّدة ومحددة جيدًا إلى الأجهزة ذات الأغراض الخاصة. المخطط العام هو أن المستند يتم تقسيمه إلى "طبقات" متعددة لا تتغير مع جوانب الصفحة التي يتم تسريعها. ويتم عرض هذه الطبقات باستخدام مسار العرض التقليدي. ثم يتم استخدام وحدة معالجة الرسومات لتركيب الطبقات في صفحة واحدة لتطبيق "التأثيرات" التي يمكن تسريعها بسرعة. النتيجة المحتملة هي أن الكائن المتحرك على الشاشة لا يتطلب "إعادة تخطيط" واحدة للصفحة أثناء حدوث الرسوم المتحركة.

ما يجب استنتاجه هو أنّكم بحاجة إلى تسهيل معرفة محرك العرض متى يمكنه تطبيق تسارع وحدة معالجة الرسومات الخاص به. راجع المثال التالي:

وبينما يعمل ذلك، لا يعرف المتصفح حقًا أنك تنفّذ شيئًا من المفترض أن ينظر إليه إنسان بحركة حركية سلسة. ضع في اعتبارك ما يحدث عندما تحقق المظهر المرئي نفسه باستخدام انتقالات CSS3 بدلاً من ذلك:

كيفية تنفيذ المتصفح لهذه الصورة المتحركة مخفية تمامًا عن مطور البرامج. وهذا يعني بدوره أن المتصفح يمكنه تطبيق حيل مثل تسريع وحدة معالجة الرسومات لتحقيق الهدف المحدد.

هناك علامتا سطر أوامر مفيدتان في Chrome للمساعدة في تصحيح أخطاء تسريع وحدة معالجة الرسومات:

  1. تُظهر --show-composited-layer-borders حدًا أحمر حول العناصر التي يتم التلاعب بها على مستوى وحدة معالجة الرسومات. جيد لتأكيد حدوث عمليات المعالجة داخل طبقة وحدة معالجة الرسومات.
  2. --show-paint-rects يتم تنفيذ جميع التغييرات غير المتعلّقة بوحدة معالجة الرسومات، ما يؤدي إلى ظهور حدود فاتحة حول جميع المناطق التي تتم إعادة طلاءها. يمكنك الاطّلاع على المتصفّح وهو يحسّن أجزاء العرض خلال تنفيذها.

يتضمّن Safari علامات وقت تشغيل مشابهة موضَّحة هنا.

انتقالات CSS3

تجعل عمليات نقل CSS صورة متحركة بسيطة للجميع، ولكنها أيضًا ميزة أداء ذكية. نظرًا لأنه تتم إدارة عملية انتقال CSS من خلال المتصفح، يمكن تحسين دقة الرسوم المتحركة إلى حد كبير، وفي كثير من الحالات تسريع الأجهزة. تحتوي أدوات WebKit حاليًا (في Chrome وSafari وiOS) على عمليات تحويل CSS يتم تسريعها باستخدام الأجهزة، ولكنها تتوفر بسرعة في المتصفحات والأنظمة الأساسية الأخرى.

يمكنك استخدام أحداث transitionEnd لتحويل ذلك إلى تركيبات فعّالة، على الرغم من أنّ تسجيل جميع أحداث نهاية عمليات النقل المتوافقة يعني في الوقت الحالي مشاهدة webkitTransitionEnd transitionend oTransitionEnd.

قدّمت العديد من المكتبات الآن واجهات برمجة تطبيقات للصور المتحركة تستفيد من الانتقالات إن توفّرت، ويعود تاريخها إلى استخدام الصور المتحركة بنمط DOM العادي. scripty2 ونقل YUI والرسوم المتحركة على jQuery محسّنة.

ترجمة CSS3

أنا متأكد من أنك وجدت نفسك من قبل تقوم بتحريك الموضع x/y لعنصر عبر الصفحة. من المحتمل أنّك تلاعبت بالخصائص اليسرى والأهم للنمط المضمّن. من خلال التحويلات الثنائية الأبعاد، يمكننا استخدام وظيفة translate() لتكرار هذا السلوك.

يمكننا الجمع بين هذا مع رسوم DOM المتحركة لاستخدام أفضل شيء ممكن

<div style="position:relative; height:120px;" class="hwaccel">

  <div style="padding:5px; width:100px; height:100px; background:papayaWhip;
              position:absolute;" id="box">
  </div>
</div>

<script>
document.querySelector('#box').addEventListener('click', moveIt, false);

function moveIt(evt) {
  var elem = evt.target;

  if (Modernizr.csstransforms && Modernizr.csstransitions) {
    // vendor prefixes omitted here for brevity
    elem.style.transition = 'all 3s ease-out';
    elem.style.transform = 'translateX(600px)';

  } else {
    // if an older browser, fall back to jQuery animate
    jQuery(elem).animate({ 'left': '600px'}, 3000);
  }
}
</script>

نستخدم Modernizr لعرض اختبار عمليات التحويل الثنائي الأبعاد CSS وانتقالات CSS، إذا كنا سنستخدم الترجمة لتغيير الموضع. إذا كانت هذه الرسوم المتحركة باستخدام عملية انتقالية، فهناك احتمال كبير أن يتمكن المتصفح من تسريع الأجهزة. لتوجيه المتصفح إلى الاتجاه الصحيح، سنستخدم "رمز CSS النقطي السحرية" من الأعلى.

إذا كان متصفحنا أقل قدرة، فسنرجع إلى jQuery لنقل العنصر. يمكنك اختيار المكوّن الإضافي jQuery Transform polyfill من قِبل "لويس ريمي بيب" لتنفيذ هذه العملية تلقائيًا.

window.requestAnimationFrame

تم تقديم requestAnimationFrame من قِبل Mozilla وتكراره بواسطة WebKit بهدف توفير واجهة برمجة تطبيقات أصلية لتشغيل الصور المتحركة، سواء كانت تعتمد على DOM/CSS أو على <canvas> أو WebGL. يمكن للمتصفح تحسين الرسوم المتحركة المتزامنة معًا في دورة واحدة لإعادة التدفق وإعادة العرض، مما يؤدي إلى استخدام رسوم متحركة بدقة أعلى. على سبيل المثال، الصور المتحركة المستندة إلى JavaScript التي تتم مزامنتها مع انتقالات CSS أو SVG SMIL. بالإضافة إلى ذلك، إذا كنت تُشغِّل حلقة الصور المتحركة في علامة تبويب غير مرئية، لن يواصل المتصفح تشغيلها، ما يقلل من استخدام وحدة المعالجة المركزية (CPU) ووحدة معالجة الرسومات (GPU) والذاكرة، ما يؤدي إلى عمر بطارية أطول.

للحصول على مزيد من التفاصيل حول كيفية استخدام ميزة "requestAnimationFrame" وسبب استخدامها، يمكنك الاطّلاع على مقالة requestAnimationFrame للحصول على صور متحركة ذكية من قناة "بول أيرش".

إنشاء ملفات تعريف ارتباط

عندما تكتشف أنه يمكن تحسين سرعة تطبيقك، يحين وقت البحث في التحليل عن كيفية اكتشاف التحسينات التي يمكن أن تحقق أكبر فائدة. غالبًا ما يكون للتحسينات تأثير سلبي على قابلية صيانة رمز المصدر وبالتالي يجب تطبيقها فقط عند الضرورة. يخبرك التحليل بأجزاء التعليمات البرمجية الخاصة بك التي ستحقق أكبر الفوائد عند تحسين أدائها.

تحليل JavaScript

تقدم لك برامج تحليل JavaScript نظرة عامة حول أداء تطبيقك على مستوى وظيفة JavaScript من خلال قياس الوقت المستغرق لتنفيذ كل دالة على حدة من بدايتها إلى نهايتها.

إجمالي وقت تنفيذ الدالة هو الوقت الإجمالي الذي يستغرقه تنفيذها من أعلى إلى أسفل. صافي وقت التنفيذ هو إجمالي وقت التنفيذ مطروحًا منه الوقت المستغرَق لتنفيذ الدوال التي تم استدعاءها من الدالة.

يتم استدعاء بعض الدوال بمعدل أكبر من غيرها. يمنحك المحللون عادةً الوقت الذي استغرقته لتنفيذ جميع الاستدعاءات بالإضافة إلى متوسط والحد الأدنى والحد الأقصى لوقت التنفيذ.

لمزيد من التفاصيل، يمكنك الاطّلاع على مستندات أدوات مطوّري البرامج في Chrome حول إنشاء الملفات التعريفية.

نموذج كائن المستند (DOM)

يؤثر أداء JavaScript بشكل قوي في مدى سلاسة تطبيقك وتجاوبه. من المهم أن تفهم أنّه على الرغم من أنّ محلّل JavaScript يقيس وقت تنفيذ JavaScript، فإنّها تقيس بشكل غير مباشر الوقت المستغرق في إجراء عمليات DOM. وغالبًا ما تكون عمليات نموذج العناصر في المستند (DOM) هذه أساس مشاكل الأداء.

function drawArray(array) {
  for(var i = 0; i < array.length; i++) {
    document.getElementById('test').innerHTML += array[i]; // No good :(
  }
}

على سبيل المثال، لا يتم استغراق أي وقت تقريبًا في تنفيذ JavaScript الفعلي في الرمز أعلاه. لا يزال هناك احتمال كبير أن تظهر دالة drawArray في ملفاتك الشخصية لأنها تتفاعل مع نموذج العناصر في المستند (DOM) بطريقة مهدرة للغاية.

النصائح

الدوال المجهولة

ليس من السهل إنشاء ملف تعريف للدوال المجهولة لأنها في طبيعتها ليس لها اسم يمكن أن تظهر به في المحلل. هناك طريقتان للتغلب على هذا الأمر:

$('.stuff').each(function() { ... });

إعادة الكتابة إلى:

$('.stuff').each(function workOnStuff() { ... });

ليس من المعروف بشكل شائع أن JavaScript تدعم تعبيرات دوال التسمية. سيؤدي القيام بذلك إلى ظهورها بشكل مثالي في المحلل. توجد مشكلة واحدة في هذا الحل: يضع التعبير المُسمّى اسم الدالة في النطاق المعجمي الحالي. قد يؤدي هذا إلى حجب الرموز الأخرى، لذا كن حذرًا.

تحليل الدوال الطويلة

تخيل أن لديك دالة طويلة وتشتبه في أن جزءًا صغيرًا منها قد يكون السبب في مشكلات الأداء. هناك طريقتان لمعرفة الجزء الذي يمثل المشكلة:

  1. الطريقة الصحيحة: أعِد ضبط التعليمة البرمجية كي لا تتضمن أي دوال طويلة.
  2. طريقة "إنجاز المهام" الشريرة: أضف عبارات في شكل وظائف استدعاء ذاتية مُسمّاة إلى التعليمة البرمجية. إذا كنت حريصًا بعض الشيء، لن يغير هذا من الدلالة ويجعل أجزاءً من الدالة تظهر كدوال فردية في المحلل: js function myLongFunction() { ... (function doAPartOfTheWork() { ... })(); ... } لا تنسَ إزالة هذه الدوال الإضافية بعد الانتهاء من التحليل، أو حتى استخدامها كنقطة بداية لإعادة بناء الرمز البرمجي.

تحليل نموذج العناصر في المستند (DOM)

تحتوي أحدث أدوات التطوير الخاصة بأداة فحص الويب من Chrome على ميزة "عرض المخطط الزمني" الجديدة التي تُظهر مخططًا زمنيًا للإجراءات المنخفضة المستوى التي ينفّذها المتصفّح. يمكنك استخدام هذه المعلومات لتحسين عمليات DOM. يجب أن تهدف إلى تقليل عدد "الإجراءات" التي يتعين على المتصفح تنفيذها أثناء تنفيذ التعليمة البرمجية.

يمكن لعرض المخطط الزمني توفير كمية هائلة من المعلومات. وبالتالي يجب أن تحاول إنشاء الحد الأدنى من حالات الاختبار التي يمكنك تنفيذها بشكل مستقل.

تحليل نموذج العناصر في المستند (DOM)

تُظهر الصورة أعلاه ناتج "عرض المخطط الزمني" لنص برمجي بسيط جدًا. تعرض اللوحة اليمنى العمليات التي ينفّذها المتصفّح بترتيب تسلسلي، بينما يعرض المخطط الزمني في الجزء الأيسر الوقت الفعلي الذي تستغرقه عملية فردية.

مزيد من المعلومات حول "عرض المخطط الزمني" إصدار DynaTrace Ajax كأداة بديلة للتحليل في Internet Explorer.

استراتيجيات إنشاء الملفات التعريفية

تحديد الجوانب

عندما تريد إضافة ملف تعريفي لتطبيقك، حاوِل تحديد جوانب وظائفه التي قد تؤدي إلى حدوث تباطؤ في أقرب وقت ممكن. بعد ذلك، حاول تشغيل ملف شخصي لا ينفذ سوى أجزاء من التعليمة البرمجية ذات الصلة بهذه الجوانب من تطبيقك. سيؤدي هذا إلى تسهيل تفسير بيانات التحليل لأنها غير مختلطة مع مسارات التعليمات البرمجية التي لا تتعلق بمشكلتك الفعلية. في ما يلي بعض الأمثلة الجيدة على الجوانب الفردية لطلب تطبيقك:

  1. وقت بدء التشغيل (تفعيل محلل الملفات الشخصية، وإعادة تحميل التطبيق، والانتظار حتى تكتمل عملية الإعداد، وإيقاف المحلل
  2. انقر فوق أحد الأزرار والرسوم المتحركة اللاحقة (بدء المحلل، انقر على الزر، انتظر حتى تكتمل الرسوم المتحركة، إيقاف المحلل).
إنشاء بيانات وصفية من واجهة المستخدم الرسومية

قد يكون تنفيذ الجزء الصحيح فقط من التطبيق أصعب في برنامج واجهة المستخدم الرسومية أكثر من تحسين متتبع الأشعة في المحرك الثلاثي الأبعاد على سبيل المثال. عندما ترغب مثلاً في تحليل الأمور التي تحدث عند النقر فوق أحد الأزرار، قد تقوم بتشغيل أحداث تمرير الماوس غير ذات صلة طوال العملية، والتي تجعل نتائجك أقل استنتاجًا. حاول تجنب ذلك :)

الواجهة الآلية

هناك أيضًا واجهة برمجية لتفعيل برنامج تصحيح الأخطاء. يتيح ذلك التحكّم بدقة في وقت بدء إنشاء الملفات التعريفية ووقت انتهائها.

بدء إنشاء ملف تعريفي باستخدام:

console.profile()

إيقاف إنشاء ملفات تعريفية باستخدام:

console.profileEnd()

إمكانية التكرار

عند إجراء التحليل، تأكد من أنه يمكنك إعادة إنتاج نتائجك. عندها فقط ستكون قادرًا على معرفة ما إذا كانت تحسيناتك قد حسّنت الأمور بالفعل أم لا. ويتم أيضًا تحديد مواصفات مستوى الوظيفة في سياق جهاز الكمبيوتر بالكامل. إنه ليس علمًا دقيقًا. وقد تتأثر عمليات تشغيل الملف الشخصي الفردي بالعديد من الأشياء الأخرى التي تحدث على جهاز الكمبيوتر:

  1. موقّت غير ذي صلة في تطبيقك الخاص بك يتم تشغيله أثناء قياس شيء آخر.
  2. يقوم جامع النفايات بعمله.
  3. هناك علامة تبويب أخرى في متصفّحك تؤدي عملاً شاقًا في سلسلة التشغيل نفسها.
  4. هناك برنامج آخر على جهاز الكمبيوتر يستهلك وحدة المعالجة المركزية (CPU)، ما يؤدي إلى إبطاء تطبيقك.
  5. تغييرات مفاجئة في مجال الجاذبية للأرض.

من المنطقي أيضًا تنفيذ نفس مسار التعليمة البرمجية عدة مرات في جلسة تحليل واحدة. بهذه الطريقة، تقلل من تأثير العوامل المذكورة أعلاه وقد تبرز الأجزاء البطيئة بشكل أكثر وضوحًا.

القياس والتحسين والقياس

عندما حددت تباطؤًا في برنامجك، حاول التفكير في طرق لتحسين سلوك التنفيذ. بعد تغيير الرمز، سيظهر لك الملف الشخصي مرة أخرى. إذا كنت راضيًا عن النتيجة، استمر، وإذا لم تجد أي تحسُّن، فمن المحتمل أن تتراجع عن التغيير ولا تتركه في "لأنه لا يضر".

استراتيجيات التحسين

تقليل تفاعل DOM

هناك موضوع شائع لتحسين سرعة تطبيقات عميل الويب، وهو تقليل تفاعل DOM. على الرغم من ازدياد سرعة محركات JavaScript بمقدار قيمة كبيرة، لم يكُن الوصول إلى DOM أسرع بالمعدل نفسه. هذا أيضًا لأسباب عملية جدًا لن تحدث أبدًا (تستغرق أشياء مثل التخطيط ورسم الأشياء على الشاشة بعض الوقت).

عُقد DOM في ذاكرة التخزين المؤقت

عند استرداد عقدة أو قائمة من العُقد من نموذج العناصر في المستند (DOM)، حاول التفكير في ما إذا كان بإمكانك إعادة استخدامها في عملية حسابية لاحقة (أو حتى التكرار الحلقي التالي فقط). وما دمت لا تضيف عُقدًا أو تحذفها في المنطقة ذات الصلة، فإن هذا هو الحال غالبًا.

قبل:

function getElements() {
  return $('.my-class');
}

بعد:

var cachedElements;
function getElements() {
  if (cachedElements) {
    return cachedElements;
  }
  cachedElements = $('.my-class');
  return cachedElements;
}

قيم سمات ذاكرة التخزين المؤقت

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

قبل:

setInterval(function() {
  var ele = $('#element');
  var left = parseInt(ele.css('left'), 10);
  ele.css('left', (left + 5) + 'px');
}, 1000 / 30);

بعد: js var ele = $('#element'); var left = parseInt(ele.css('left'), 10); setInterval(function() { left += 5; ele.css('left', left + 'px'); }, 1000 / 30);

نقل معالجة DOM خارج التكرارات

غالبًا ما تكون التكرارات الحلقية نقاطًا سريعة للتحسين. حاول التفكير في طرق للفصل بين معالجة العدد الفعلي والتعامل مع نموذج العناصر في المستند (DOM). غالبًا ما يكون من الممكن إجراء عملية حسابية، ثم بعد الانتهاء منها، تطبيق جميع النتائج دفعة واحدة.

قبل:

document.getElementById('target').innerHTML = '';
for(var i = 0; i < array.length; i++) {
  var val = doSomething(array[i]);
  document.getElementById('target').innerHTML += val;
}

بعد:

var stringBuilder = [];
for(var i = 0; i < array.length; i++) {
  var val = doSomething(array[i]);
  stringBuilder.push(val);
}
document.getElementById('target').innerHTML = stringBuilder.join('');

عمليات إعادة الرسم وعمليات إعادة التدفق

كما سبق وذكرنا، يكون الوصول إلى DOM بطيء نسبيًا. يصبح الأمر بطيئًا جدًا عندما تقرأ التعليمة البرمجية قيمة يجب إعادة حسابها لأن التعليمة البرمجية قد عدَّلت مؤخرًا شيئًا ذي صلة في DOM. وبالتالي، يجب تجنُّب استخدام الإذن بالقراءة والكتابة في نموذج العناصر في المستند (DOM). من الناحية المثالية، يجب تجميع التعليمة البرمجية دائمًا في مرحلتين:

  • المرحلة 1: قراءة قيم DOM اللازمة للرمز البرمجي
  • المرحلة 2: تعديل DOM

حاول عدم برمجة نمط مثل:

  • المرحلة 1: قراءة قيم DOM
  • المرحلة 2: تعديل DOM
  • المرحلة 3: قراءة بعض المعلومات الإضافية
  • المرحلة 4: تعديل DOM في مكان آخر.

قبل:

function paintSlow() {
  var left1 = $('#thing1').css('left');
  $('#otherThing1').css('left', left);
  var left2 = $('#thing2').css('left');
  $('#otherThing2').css('left', left);
}

بعد:

function paintFast() {
  var left1 = $('#thing1').css('left');
  var left2 = $('#thing2').css('left');
  $('#otherThing1').css('left', left);
  $('#otherThing2').css('left', left);
}

ويجب مراعاة هذه النصيحة عند تنفيذ الإجراءات التي تحدث ضمن سياق واحد لتنفيذ JavaScript. (على سبيل المثال، داخل معالج الأحداث، أو ضمن معالج الفاصل الزمني أو عند التعامل مع استجابة ajax)

يؤدي تنفيذ الدالة paintSlow() من أعلى إلى إنشاء هذه الصورة:

paintSlow()

يؤدي التبديل إلى التنفيذ الأسرع إلى ظهور هذه الصورة:

التنفيذ بشكل أسرع

توضّح هذه الصور أنّ إعادة ترتيب طريقة وصول الرمز إلى نموذج العناصر في المستند (DOM) يمكن أن يؤدي إلى تحسين أداء العرض بشكل كبير. في هذه الحالة، يتعين على التعليمة البرمجية الأصلية إعادة حساب الأنماط وتنسيق الصفحة مرتين لإنشاء نفس النتيجة. يمكن تطبيق التحسين المشابه على كل الرموز البرمجية "الحقيقية" أساسًا، ما يؤدي إلى تحقيق بعض النتائج المثيرة للغاية.

قراءة المزيد: العرض: إعادة الطلاء، إعادة التدفق/إعادة التنسيق، إعادة التصميم من تأليف "ستويان ستيفانوف"

عمليات إعادة الرسم وتكرار الأحداث

تتّبِع عملية تنفيذ JavaScript في المتصفِّح نموذج "حلقة الأحداث". يكون المتصفِّح في حالة "غير نشِط لفترة قصيرة" تلقائيًا. ويمكن إيقاف هذه الحالة بسبب أحداث ناتجة عن تفاعلات المستخدم أو عناصر مثل موقّتات JavaScript أو عمليات استدعاء Ajax. عند تشغيل جزء من JavaScript عند نقطة المقاطعة هذه، سينتظر المتصفح عادةً حتى ينتهي إلى أن يعيد عرض الشاشة (قد تكون هناك استثناءات لتشغيل برامج JavaScript لفترات طويلة جدًا أو في حالات مثل مربّعات التنبيه التي تقاطع عملية تنفيذ JavaScript بشكل فعّال).

النتائج

  1. إذا استغرق تنفيذ صور JavaScript المتحركة وقتًا أطول من 1/30 ثانية، لن تتمكّن من إنشاء صور متحركة متجانسة لأنّ المتصفِّح لن تتم إعادة عرضه أثناء تنفيذ JavaScript. عندما تتوقع أيضًا التعامل مع أحداث المستخدم، يجب أن تكون أسرع بكثير.
  2. في بعض الأحيان، قد يكون من المفيد تأخير بعض إجراءات JavaScript حتى وقت لاحق. مثال: setTimeout(function() { ... }, 0) يطلب ذلك من المتصفِّح تنفيذ معاودة الاتصال عندما يصبح حلقة الأحداث غير نشِطة مرة أخرى (علمًا أنّ بعض المتصفحات ستنتظر 10 ملي ثانية على الأقل). يجب أن تدرك أن هذا الإجراء سيؤدي إلى إنشاء دورتين لتنفيذ JavaScript قريبتين جدًا من بعضهما البعض. قد يؤدي كلاهما إلى إعادة طلاء الشاشة مما قد يضاعف الوقت الإجمالي المستغرق في الطلاء. يعتمد ما إذا كان هذا الإجراء يؤدي إلى عرض نسختَين من الصفحة على إشارات في المتصفّح.

الإصدار العادي:

function paintFast() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  $('#otherThing2').css('height', '20px');
}
عمليات إعادة الرسم وتكرار الأحداث

لنضيف بعض التأخير:

function paintALittleLater() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  setTimeout(function() {
    $('#otherThing2').css('height', '20px');
  }, 10)
}
تأخير

يوضح الإصدار المتأخر أن المتصفح يرسم مرتين على الرغم من أن التغييرين اللذين يتم إجراؤهما على الصفحة هما 1/100 من الثانية فقط.

الإعداد الكسول

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

وبالتالي، قد يكون من المنطقي نقل رمز الإعداد ليتم تنفيذه في وقت متأخر قدر الإمكان (على سبيل المثال، عندما ينقر المستخدم على زر ينشّط مكوِّنًا معينًا في تطبيقك).

قبل: js var things = $('.ele > .other * div.className'); $('#button').click(function() { things.show() });

بعد: js $('#button').click(function() { $('.ele > .other * div.className').show() });

تفويض الحدث

وقد يستغرق نشر معالِجات الأحداث على الصفحة وقتًا طويلاً نسبيًا، ويمكن أن يكون أيضًا مملاً عند استبدال العناصر بشكل ديناميكي، الأمر الذي يتطلب بعد ذلك إعادة إرفاق معالِجات الأحداث إلى العناصر الجديدة.

يكمن الحل في هذه الحالة في استخدام أسلوب يسمّى تفويض الحدث. بدلاً من إرفاق معالِجات الأحداث الفردية بالعناصر، يتم استخدام طبيعة الفقاعات التفسيرية للعديد من أحداث المتصفِّح من خلال إرفاق معالج الحدث بعقدة رئيسية والتحقّق من العقدة المستهدفة للحدث لمعرفة ما إذا كان الحدث مهمًا أم لا.

في jQuery، يمكن التعبير عن ذلك بسهولة على النحو التالي:

$('#parentNode').delegate('.button', 'click', function() { ... });

الحالات التي لا يجب فيها استخدام تفويض الحدث

وفي بعض الأحيان قد يكون العكس صحيحًا: عندما تستخدم تفويض الحدث وتواجه مشكلة في الأداء. يسمح تفويض الحدث في الأساس بوقت تهيئة مستوى التعقيد المستمر. ومع ذلك، يجب دفع سعر التحقق ممّا إذا كان الحدث المعنيّ مقابل كل استدعاء لهذا الحدث. قد يكون ذلك مكلفًا، خاصةً في الأحداث التي تتم بشكل متكرر مثل "mouseover" أو "mousemove".

المشاكل النموذجية والحلول

يستغرق تنفيذ الإجراءات في $(document).ready وقتًا طويلاً.

نصيحة شخصية من مالتي: يجب عدم اتّخاذ أي إجراء في $(document).ready. حاول تقديم المستند بشكله النهائي. حسنًا، يُسمح لك بتسجيل أدوات معالجة الأحداث، ولكن باستخدام أداة اختيار المعرّف و/أو استخدام تفويض الحدث فقط. بالنسبة إلى الأحداث المكلفة، مثل "mousemove"، يمكنك تأخير التسجيل إلى أن تكون هناك حاجة إليها (حدث تمرير الماوس على العنصر ذي الصلة).

وإذا كنت حقًا بحاجة للقيام بأشياء، مثل تقديم طلب Ajax للحصول على بيانات فعلية، ثم عرض رسم متحرك رائع؛ قد ترغب في تضمين الرسوم المتحركة كمعرف موارد بيانات (URI) للبيانات إذا كان ملف GIF متحرك أو ما شابه.

نظرًا لأنني أضفت فيلم فلاش إلى الصفحة، فإن كل شيء بطيء جدًا

تؤدي إضافة Flash إلى إحدى الصفحات دائمًا إلى إبطاء العرض قليلاً لأنه يجب "التفاوض" على التنسيق النهائي للنافذة بين المتصفح والمكوّن الإضافي Flash. عندما يتعذّر عليك تجنُّب وضع Flash تمامًا على صفحاتك، احرص على ضبط معلَمة Flash "wmode" على قيمة "window" (وهي القيمة التلقائية). سيؤدي ذلك إلى تعطيل القدرة على تركيب عناصر HTML وFlash (لن تتمكن من رؤية عنصر HTML أعلى فيلم الفلاش ولا يمكن أن يكون فيلم الفلاش شفافًا). قد يمثل هذا الأمر إزعاجًا لك، ولكنه سيحسن أدائك إلى حد كبير. على سبيل المثال، اطّلع على الطريقة التي يتجنّب بها موقع youtube.com وضع الطبقات فوق مشغّل الفيلم الرئيسي بعناية.

أريد حفظ بيانات في localStorage، والآن تطبيقي يتعثر

الكتابة في LocalStorage هي عملية متزامنة تتضمن تدوير القرص الثابت. لن ترغب أبدًا في القيام بعمليات متزامنة "لفترة طويلة" أثناء القيام بالرسوم المتحركة. انقل إمكانية الوصول إلى localStorage إلى موضع في التعليمة البرمجية حيث يمكنك التأكد من أن المستخدم غير نشط ولا توجد أي رسوم متحركة قيد التشغيل.

يشير التحليل إلى بطء أداة اختيار jQuery بشكل كبير.

عليك أولاً التأكّد من إمكانية تشغيل أداة الاختيار من خلال document.querySelectorAll. يمكنك اختبار ذلك في وحدة تحكم JavaScript. إذا كان هناك استثناء، يُرجى إعادة كتابة أداة الاختيار لعدم استخدام أي إضافة خاصة لإطار عمل JavaScript. سيؤدي هذا إلى تسريع أداة الاختيار في المتصفحات الحديثة بمقدار القيمة.

إذا لم يساعدك هذا أو إذا كنت تريد أيضًا أن تكون سريعة في المتصفحات الحديثة، اتبع هذه الإرشادات:

  • يُرجى توفير أكبر قدر ممكن من التفاصيل على الجانب الأيسر من أداة الاختيار.
  • استخدِم اسم علامة لا تستخدمه كثيرًا كجزء أداة الاختيار في أقصى يسار الصفحة.
  • إذا لم يساعدك أي حل، يُرجى إعادة كتابة المحتوى لتتمكّن من استخدام أداة اختيار رقم التعريف.

تستغرق كل عمليات معالجة DOM هذه وقتًا طويلاً.

يمكن أن تكون مجموعة من عمليات إدراج عُقد DOM وإزالتها وتحديثاتها بطيئة للغاية. ويمكن تحسين ذلك بشكل عام من خلال إنشاء سلسلة كبيرة من لغة html واستخدام domNode.innerHTML = newHTML بدلاً من المحتوى القديم. لاحظ أن هذا قد يكون سيئًا بالفعل للصيانة وقد يؤدي إلى إنشاء روابط ذاكرة في IE، لذا يجب توخي الحذر.

هناك مشكلة شائعة أخرى وهي أن رمز التهيئة قد ينشئ الكثير من محتوى HTML. على سبيل المثال، مكوّن إضافي لـ jQuery يحول مربع التحديد إلى مجموعة من علامات div لأن هذا هو التصميم الذي أراده الأشخاص مع تجاهل أفضل ممارسات تجربة المستخدم. إذا كنت حقًا تريد أن تكون صفحتك سريعة، فلا تفعل ذلك مطلقًا. بدلاً من ذلك، سلِّم كل الترميزات من جهة الخادم بشكلها النهائي. هذا به العديد من المشكلات مرة أخرى، لذا فكر مليًا فيما إذا كانت السرعة تستحق المقايضة.

الأدوات

  1. JSPerf - قياس المقتطفات الصغيرة من JavaScript
  2. Firebug: لتحديد ملفات التعريف في Firefox
  3. أدوات مطوّري برامج Google Chrome (متوفّرة في WebInspector في Safari)
  4. DoM Monster - لتحسين أداء DOM
  5. إصدار DynaTrace Ajax - لتحسين الملفات الشخصية والطلاء في Internet Explorer

محتوى إضافي للقراءة

  1. سرعة Google
  2. "بول أيرش" حول أداء jQuery
  3. الأداء العالي للغة JavaScript (مجموعة الشرائح)