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

مقدمة

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

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

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

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

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

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

تحسين الدقّة المرئية باستخدام HTML5

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

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

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

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

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

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

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

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

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

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

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

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

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

انتقالات CSS3

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

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

لقد طرحت العديد من المكتبات الآن واجهات برمجة تطبيقات للرسوم المتحركة تستفيد من الانتقالات في حال توفّرها وتستخدم الرسوم المتحركة العادية بأسلوب DOM في حال عدم توفّرها. scripty2 وYUI transition وjQuery animate enhanced.

ترجمة CSS3

من المؤكد أنّك سبق لك إضافة تأثيرات متحركة إلى موضع x/y لعنصر على الصفحة. من المحتمل أنّك عدّلت السمتَين left وtop للأسلوب المضمّن. باستخدام عمليات التحويل ثنائية الأبعاد، يمكننا استخدام وظيفة 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 2D Transforms وCSS Transitions، وإذا كان الأمر كذلك، سنستخدم translate لتغيير الموضع. إذا كان هذا العنصر متحركًا باستخدام انتقال، من المرجّح أن يتمكّن المتصفّح من تسريعه باستخدام ميزة "تسريع الأجهزة". لمنح المتصفّح دفعة أخرى في الاتجاه الصحيح، سنستخدم "رصاصة CSS السحرية" من الأعلى.

إذا كان المتصفّح أقل كفاءة، سنستخدم jQuery لنقل العنصر. يمكنك استخدام مكوّن jQuery Transform polyfill الإضافي من إنشاء Louis-Remi Babe لإجراء هذه العملية تلقائيًا.

window.requestAnimationFrame

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

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

تحديد المواصفات الشخصية لصاحب البيانات

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

تحليل JavaScript

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

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

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

لمزيد من التفاصيل، اطّلِع على مستندات "أدوات مطوّري البرامج في Chrome" حول التحليل.

نموذج عناصر المستند

يؤثر أداء 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 Web Inspector على "عرض المخطط الزمني" الجديد الذي يعرض مخططًا زمنيًا للإجراءات ذات المستوى المنخفض التي ينفّذها المتصفّح. يمكنك استخدام هذه المعلومات لتحسين عمليات DOM. يجب أن تسعى إلى تقليل عدد "الإجراءات" التي يجب أن ينفّذها المتصفّح أثناء تنفيذ الرمز.

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

تحليل نموذج DOM

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

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

استراتيجيات وضع الملفات الشخصية

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

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

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

أحفظ البيانات في localStorage، والآن يبطئ تطبيقي.

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

تشير ميزة "التحليل" إلى أنّ أداة اختيار jQuery بطيئة جدًا.

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

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

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

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

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

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

الأدوات

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

مراجع إضافية

  1. سرعة Google
  2. مقالة بول إيرلندي حول أداء jQuery
  3. تحسين أداء JavaScript بشكل كبير (مجموعة شرائح)