การปรับปรุงประสิทธิภาพแอปพลิเคชัน HTML5 ของคุณ

บทนำ

HTML5 มีเครื่องมือที่ยอดเยี่ยมในการปรับปรุงลักษณะที่ปรากฏของเว็บแอปพลิเคชัน โดยเฉพาะอย่างยิ่งในวงการภาพเคลื่อนไหว อย่างไรก็ตาม ความสามารถใหม่นี้มาพร้อมกับความท้าทายใหม่ๆ ด้วย จริงๆ แล้วปัญหาเหล่านี้ไม่ใช่เรื่องใหม่มากนัก และบางครั้งคุณอาจลองถามเพื่อนร่วมงานที่ทำงานโต๊ะข้างๆ ซึ่งเป็นโปรแกรมเมอร์ Flash ว่าเธอเคยแก้ปัญหาที่คล้ายกันนี้อย่างไรในอดีต

อย่างไรก็ตาม เมื่อคุณทำงานกับภาพเคลื่อนไหว สิ่งสำคัญอย่างยิ่งคือผู้ใช้ต้องรู้สึกว่าภาพเคลื่อนไหวเหล่านี้ราบรื่น สิ่งที่เราต้องตระหนักคือภาพเคลื่อนไหวที่ราบรื่นไม่ได้สร้างขึ้นจากการเพิ่มเฟรมต่อวินาทีให้มากกว่าเกณฑ์การรับรู้ แต่สมองของเราฉลาดกว่านั้น สิ่งที่คุณจะได้เรียนรู้คือภาพเคลื่อนไหว 30 เฟรมต่อวินาที (FPS) จริงนั้นดีกว่า 60 FPS ที่มีเฟรมตกหล่นเพียงไม่กี่เฟรมในช่วงกลาง ผู้คนไม่ชอบภาพที่มีขอบขรุขระ

บทความนี้จะพยายามให้เครื่องมือและเทคนิคในการปรับปรุงประสบการณ์การใช้งานแอปพลิเคชันของคุณเอง

กลยุทธ์

เราไม่ได้ต้องการทําให้คุณเลิกสร้างแอปที่ยอดเยี่ยมและมีภาพที่น่าทึ่งด้วย HTML5

เมื่อเห็นว่าประสิทธิภาพดีขึ้นเล็กน้อยแล้ว ให้กลับมาที่นี่และอ่านวิธีปรับปรุงองค์ประกอบของแอปพลิเคชัน แน่นอนว่าการวางแผนล่วงหน้าจะช่วยให้คุณทำงานได้อย่างถูกต้องตั้งแต่ต้น แต่อย่าปล่อยให้การวางแผนมาขัดขวางประสิทธิภาพการทำงาน

ความถูกต้องของภาพ++ ด้วย HTML5

การเร่งฮาร์ดแวร์

การเร่งด้วยฮาร์ดแวร์เป็นเหตุการณ์สำคัญสำหรับประสิทธิภาพการแสดงผลโดยรวมในเบราว์เซอร์ รูปแบบทั่วไปคือการถ่ายโอนงานที่ซีพียูหลักควรจะคำนวณไปยังหน่วยประมวลผลกราฟิก (GPU) ในอะแดปเตอร์กราฟิกของคอมพิวเตอร์ ซึ่งอาจช่วยเพิ่มประสิทธิภาพได้อย่างมากและยังช่วยลดการใช้ทรัพยากรในอุปกรณ์เคลื่อนที่ได้ด้วย

GPU สามารถเร่งการแสดงผลของเอกสารในด้านต่างๆ เหล่านี้ได้

  • การคอมโพสเลย์เอาต์ทั่วไป
  • การเปลี่ยน CSS3
  • การเปลี่ยนรูปแบบ 3 มิติ CSS3
  • การวาดภาพแคนวาส
  • ภาพวาด 3 มิติของ WebGL

แม้ว่าการเร่ง Canvas และ WebGL จะเป็นฟีเจอร์ที่มีวัตถุประสงค์พิเศษซึ่งอาจไม่เหมาะกับแอปพลิเคชันหนึ่งๆ แต่ 3 ด้านแรกจะช่วยให้แอปเกือบทุกแอปทำงานได้เร็วขึ้น

สิ่งใดบ้างที่เร่งได้

การเร่ง GPU ทำงานโดยการส่งต่องานที่เฉพาะเจาะจงและชัดเจนไปยังฮาร์ดแวร์เฉพาะ รูปแบบทั่วไปคือเอกสารจะแบ่งออกเป็น "เลเยอร์" หลายเลเยอร์ ซึ่งไม่เกี่ยวข้องกับแง่มุมของหน้าเว็บที่เร่ง เลเยอร์เหล่านี้จะแสดงผลโดยใช้ไปป์ไลน์การแสดงผลแบบดั้งเดิม จากนั้นระบบจะใช้ GPU เพื่อคอมโพสเลเยอร์ลงในหน้าเว็บเดียวโดยใช้ "เอฟเฟกต์" ที่เร่งได้ทันที ผลลัพธ์ที่เป็นไปได้คือออบเจ็กต์ที่เคลื่อนไหวบนหน้าจอไม่จําเป็นต้อง "จัดเรียงใหม่" หน้าเว็บแม้แต่ครั้งเดียวขณะที่ภาพเคลื่อนไหวเกิดขึ้น

สิ่งที่ควรทราบคือคุณต้องทําให้เครื่องมือแสดงผลระบุได้ง่ายว่าจะใช้การเร่งความเร็ว GPU เมื่อใด ลองพิจารณาตัวอย่างต่อไปนี้

แม้ว่าวิธีนี้จะได้ผล แต่เบราว์เซอร์จะไม่ทราบว่าคุณกําลังทําสิ่งที่มนุษย์ควรจะรับรู้ว่าเป็นภาพเคลื่อนไหวที่ราบรื่น ลองพิจารณาสิ่งที่จะเกิดขึ้นเมื่อคุณสร้างลักษณะที่ปรากฏเหมือนกันโดยใช้การเปลี่ยน CSS3 แทน

นักพัฒนาแอปจะไม่เห็นวิธีที่เบราว์เซอร์ใช้ภาพเคลื่อนไหวนี้ ซึ่งหมายความว่าเบราว์เซอร์สามารถใช้เทคนิคต่างๆ เช่น การเร่งด้วย GPU เพื่อให้บรรลุเป้าหมายที่กำหนดไว้

Flag บรรทัดคำสั่งที่มีประโยชน์ 2 รายการสำหรับ Chrome เพื่อช่วยแก้ไขข้อบกพร่องในการเร่งความเร็วด้วย GPU มีดังนี้

  1. --show-composited-layer-borders แสดงเส้นขอบสีแดงรอบองค์ประกอบที่มีการดัดแปลงที่ระดับ GPU เหมาะสำหรับการยืนยันว่าการดัดแปลงเกิดขึ้นภายในเลเยอร์ GPU
  2. --show-paint-rects การเปลี่ยนแปลงทั้งหมดที่ไม่ใช่ GPU จะแสดงผลและจะมีเส้นขอบสีอ่อนรอบๆ บริเวณทั้งหมดที่ทาสีใหม่ คุณสามารถดูการทำงานของเบราว์เซอร์ในการเพิ่มประสิทธิภาพพื้นที่การวาด

Safari มี Flag รันไทม์ที่คล้ายกันซึ่งอธิบายไว้ที่นี่

การเปลี่ยน CSS3

การเปลี่ยน CSS ทำให้ภาพเคลื่อนไหวของสไตล์เป็นเรื่องง่ายสำหรับทุกคน ทั้งยังเป็นฟีเจอร์ที่มีประสิทธิภาพอีกด้วย เนื่องจากเบราว์เซอร์จัดการการเปลี่ยน CSS คุณภาพของภาพเคลื่อนไหวจึงได้รับการปรับปรุงอย่างมาก และในกรณีหลายอย่างก็มีการเร่งด้วยฮาร์ดแวร์ ปัจจุบัน WebKit (Chrome, Safari, iOS) มีการเปลี่ยนรูปแบบ CSS ที่เร่งด้วยฮาร์ดแวร์ แต่ฟีเจอร์นี้จะพร้อมใช้งานในเบราว์เซอร์และแพลตฟอร์มอื่นๆ เร็วๆ นี้

คุณสามารถใช้เหตุการณ์ transitionEnd เพื่อเขียนสคริปต์นี้ให้เป็นชุดค่าผสมที่มีประสิทธิภาพ แต่ตอนนี้การบันทึกเหตุการณ์สิ้นสุดทรานซิชันที่รองรับทั้งหมดหมายถึงการดู webkitTransitionEnd transitionend oTransitionEnd

ไลบรารีหลายแห่งได้เปิดตัว API การเคลื่อนไหวที่ใช้ทรานซิชันหากมี และเปลี่ยนไปใช้การเคลื่อนไหวสไตล์ DOM มาตรฐานหากไม่มี scripty2, YUI transition, jQuery animate enhanced

CSS3 Translate

เราแน่ใจว่าคุณเคยสร้างภาพเคลื่อนไหวตำแหน่ง x/y ขององค์ประกอบในหน้าเว็บแล้ว คุณอาจแก้ไขพร็อพเพอร์ตี้ซ้ายและด้านบนของสไตล์ในบรรทัด เมื่อใช้การเปลี่ยนรูปแบบ 2 มิติ เราจะใช้ฟังก์ชัน 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 เพื่อทดสอบฟีเจอร์การเปลี่ยนรูปแบบ 2 มิติของ CSS และการเปลี่ยนรูปแบบของ CSS หากใช่ เราจะใช้ translate เพื่อเปลี่ยนตำแหน่ง หากภาพเคลื่อนไหวนี้ใช้ทรานซิชัน โอกาสที่เบราว์เซอร์จะเร่งฮาร์ดแวร์ได้ก็สูง เราจะใช้ "ลูกศร CSS วิเศษ" จากด้านบนเพื่อกระตุ้นเบราว์เซอร์ให้ไปในทิศทางที่ถูกต้อง

หากเบราว์เซอร์มีประสิทธิภาพต่ำกว่า เราจะเปลี่ยนไปใช้ jQuery เพื่อย้ายองค์ประกอบ คุณสามารถใช้ปลั๊กอิน polyfill ของ jQuery Transform โดย Louis-Remi Babe เพื่อให้การดำเนินการทั้งหมดนี้เป็นแบบอัตโนมัติ

window.requestAnimationFrame

requestAnimationFrame เปิดตัวโดย Mozilla และ WebKit พัฒนาต่อโดยมีเป้าหมายเพื่อมอบ API เนทีฟสำหรับการแสดงภาพเคลื่อนไหว ไม่ว่าจะเป็นแบบ DOM/CSS หรือ <canvas> หรือ WebGL เบราว์เซอร์สามารถเพิ่มประสิทธิภาพภาพเคลื่อนไหวที่เกิดขึ้นพร้อมกันให้เป็นรอบการจัดเรียงใหม่และการวาดภาพใหม่รอบเดียว ซึ่งจะทำให้ภาพเคลื่อนไหวมีความเที่ยงตรงมากขึ้น เช่น ภาพเคลื่อนไหวที่ใช้ JS ซึ่งซิงค์กับการเปลี่ยน CSS หรือ SVG SMIL นอกจากนี้ หากคุณเปิดภาพเคลื่อนไหวแบบวนซ้ำในแท็บที่มองไม่เห็น เบราว์เซอร์จะไม่เปิดภาพเคลื่อนไหวนั้นไว้ ซึ่งหมายความว่าจะมีการใช้ CPU, GPU และหน่วยความจำน้อยลง ซึ่งทำให้แบตเตอรี่ใช้งานได้นานขึ้น

ดูรายละเอียดเพิ่มเติมเกี่ยวกับวิธีและเหตุผลในการใช้ requestAnimationFrame ได้ที่บทความ requestAnimationFrame สำหรับภาพเคลื่อนไหวอัจฉริยะของ Paul Irish

การทำโปรไฟล์

เมื่อพบว่าความเร็วของแอปพลิเคชันสามารถปรับปรุงได้ ก็ถึงเวลาเจาะลึกการโปรไฟล์เพื่อดูว่าการเพิ่มประสิทธิภาพที่ใดจะให้ประโยชน์สูงสุด การเพิ่มประสิทธิภาพมักจะส่งผลเสียต่อความสามารถในการบำรุงรักษาซอร์สโค้ด จึงควรใช้ในกรณีที่จําเป็นเท่านั้น โปรไฟล์จะบอกให้ทราบว่าส่วนใดของโค้ดที่จะให้ประโยชน์สูงสุดเมื่อปรับปรุงประสิทธิภาพ

การสร้างโปรไฟล์ 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 ในลักษณะที่สิ้นเปลืองมาก

กลเม็ดเคล็ดลับ

ฟังก์ชันนิรนาม

โปรไฟล์ฟังก์ชันที่ไม่ระบุชื่อนั้นทำได้ยาก เนื่องจากฟังก์ชันเหล่านี้ไม่มีชื่อที่จะแสดงในเครื่องมือวิเคราะห์โปรไฟล์ คุณสามารถแก้ปัญหานี้ได้ 2 วิธีดังนี้

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

เขียนใหม่เป็น

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

ไม่ค่อยมีใครทราบว่า JavaScript รองรับการตั้งชื่อนิพจน์ฟังก์ชัน ซึ่งจะทำให้รายการดังกล่าวแสดงในเครื่องมือวิเคราะห์ได้อย่างสมบูรณ์ โซลูชันนี้มีปัญหาอยู่ 1 อย่างคือ นิพจน์ที่มีชื่อจะใส่ชื่อฟังก์ชันไว้ในขอบเขตเชิงศัพท์ปัจจุบัน โปรดระมัดระวังเนื่องจากอาจบดบังสัญลักษณ์อื่นๆ

การทำโปรไฟล์ฟังก์ชันที่ใช้เวลานาน

สมมติว่าคุณมีฟังก์ชันที่ยาวและสงสัยว่าฟังก์ชันส่วนเล็กๆ อาจเป็นสาเหตุของปัญหาด้านประสิทธิภาพ คุณสามารถดูว่าส่วนใดเป็นปัญหาได้ 2 วิธีดังนี้

  1. วิธีแก้ไขที่ถูกต้อง: ปรับโค้ดใหม่เพื่อไม่ให้มีฟังก์ชันที่ยาว
  2. วิธีแก้ปัญหาที่ไม่ดี: เพิ่มคำสั่งในรูปแบบของฟังก์ชันที่เรียกตัวเองที่มีชื่อลงในโค้ด หากคุณระมัดระวังสักหน่อย การดำเนินการนี้จะไม่เปลี่ยนความหมายและทำให้ฟังก์ชันบางส่วนแสดงเป็นฟังก์ชันเดี่ยวในเครื่องมือวิเคราะห์โปรไฟล์ js function myLongFunction() { ... (function doAPartOfTheWork() { ... })(); ... } อย่าลืมนําฟังก์ชันพิเศษเหล่านี้ออกหลังจากการวิเคราะห์โปรไฟล์เสร็จแล้ว หรือจะใช้เป็นจุดเริ่มต้นในการรีแฟกทอริงโค้ดก็ได้

การทำโปรไฟล์ DOM

เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ Chrome Web Inspector เวอร์ชันล่าสุดมี "มุมมองไทม์ไลน์" ใหม่ซึ่งแสดงไทม์ไลน์ของการดำเนินการระดับต่ำที่เบราว์เซอร์ดำเนินการ คุณสามารถใช้ข้อมูลนี้เพื่อเพิ่มประสิทธิภาพการดําเนินการ DOM คุณควรมุ่งลดจํานวน "การดําเนินการ" ที่เบราว์เซอร์ต้องทําขณะที่โค้ดของคุณดําเนินการ

มุมมองไทม์ไลน์สามารถสร้างข้อมูลจํานวนมาก คุณจึงควรสร้างเทสเคสจำนวนน้อยที่สุดที่คุณดำเนินการได้เอง

การทำโปรไฟล์ DOM

รูปภาพด้านบนแสดงเอาต์พุตของมุมมองไทม์ไลน์สําหรับสคริปต์ที่เรียบง่ายมาก แผงด้านซ้ายแสดงการดำเนินการที่เบราว์เซอร์ดำเนินการตามลำดับเวลา ขณะที่ไทม์ไลน์ในแผงด้านขวาแสดงเวลาจริงที่ใช้ในการดำเนินการแต่ละรายการ

ข้อมูลเพิ่มเติมเกี่ยวกับมุมมองไทม์ไลน์ เครื่องมือสําหรับสร้างโปรไฟล์ใน Internet Explorer อีกอย่างคือ DynaTrace Ajax Edition

กลยุทธ์การจัดทำโปรไฟล์

เลือกแง่มุม

เมื่อต้องการโปรไฟล์แอปพลิเคชัน ให้ลองแยกแง่มุมของฟังก์ชันการทำงานที่อาจทริกเกอร์ความช้าให้มากที่สุด จากนั้นลองเรียกใช้โปรไฟล์ที่ดำเนินการเฉพาะโค้ดบางส่วนที่เกี่ยวข้องกับแง่มุมเหล่านี้ของแอปพลิเคชัน วิธีนี้จะช่วยให้ตีความข้อมูลการโปรไฟล์ได้ง่ายขึ้น เนื่องจากไม่ได้ปะปนกับเส้นทางโค้ดที่ไม่เกี่ยวข้องกับปัญหาจริง ตัวอย่างที่ดีสำหรับแต่ละแง่มุมของแอปพลิเคชัน ได้แก่

  1. เวลาเริ่มต้น (เปิดใช้งานเครื่องมือวิเคราะห์ โหลดแอปพลิเคชันอีกครั้ง รอจนกว่าการเริ่มต้นจะเสร็จสมบูรณ์ หยุดเครื่องมือวิเคราะห์
  2. คลิกปุ่มและภาพเคลื่อนไหวที่ตามมา (เริ่มเครื่องมือวิเคราะห์ คลิกปุ่ม รอจนกว่าภาพเคลื่อนไหวจะเสร็จสิ้น หยุดเครื่องมือวิเคราะห์)
การทำโปรไฟล์ GUI

การดำเนินการเฉพาะส่วนที่เหมาะสมของแอปพลิเคชันในโปรแกรม GUI อาจทำได้ยากกว่าเมื่อคุณเพิ่มประสิทธิภาพ เช่น เรย์แทร็กเกอร์ของเอ็นจิ้น 3 มิติ เช่น เมื่อต้องการสร้างโปรไฟล์สิ่งที่เกิดขึ้นเมื่อคุณคลิกปุ่ม คุณอาจทริกเกอร์เหตุการณ์การเลื่อนเมาส์เหนือที่ไม่เกี่ยวข้องระหว่างทาง ซึ่งทําให้ผลลัพธ์ไม่น่าเชื่อถือ โปรดหลีกเลี่ยงการทำเช่นนั้น :)

อินเทอร์เฟซแบบเป็นโปรแกรม

นอกจากนี้ยังมีอินเทอร์เฟซแบบเป็นโปรแกรมเพื่อเปิดใช้งานโปรแกรมแก้ไขข้อบกพร่องด้วย วิธีนี้ช่วยให้คุณควบคุมได้อย่างแม่นยำว่าโปรไฟล์จะเริ่มและสิ้นสุดเมื่อใด

เริ่มสร้างโปรไฟล์ด้วย

console.profile()

หยุดทำโปรไฟล์ด้วย

console.profileEnd()

ความสามารถในการทำซ้ำ

เมื่อทำโปรไฟล์ ให้ตรวจสอบว่าคุณทำซ้ำผลลัพธ์ได้จริง เมื่อถึงเวลานั้น คุณถึงจะทราบว่าการเพิ่มประสิทธิภาพช่วยปรับปรุงสิ่งต่างๆ ได้จริงหรือไม่ นอกจากนี้ โปรไฟล์ระดับฟังก์ชันจะดำเนินการในบริบทของทั้งคอมพิวเตอร์ ข้อมูลนี้ไม่ใช่วิทยาศาสตร์ที่แน่นอน การทำงานของโปรไฟล์แต่ละรายการอาจได้รับอิทธิพลจากสิ่งอื่นๆ อีกมากมายที่เกิดขึ้นในคอมพิวเตอร์

  1. ตัวจับเวลาที่ไม่เกี่ยวข้องในแอปพลิเคชันของคุณเองซึ่งเริ่มทำงานขณะที่คุณวัดค่าอื่น
  2. Garbage Collector กำลังทำงาน
  3. แท็บอื่นในเบราว์เซอร์ที่ทำงานอย่างหนักในชุดข้อความการทํางานเดียวกัน
  4. โปรแกรมอื่นในคอมพิวเตอร์ใช้ CPU อยู่ ทำให้แอปพลิเคชันทำงานช้าลง
  5. การเปลี่ยนแปลงอย่างฉับพลันของสนามแรงโน้มถ่วงของโลก

นอกจากนี้ การดำเนินการเส้นทางโค้ดเดียวกันหลายครั้งในเซสชันการโปรไฟล์ 1 รายการก็เป็นเรื่องที่สมเหตุสมผล วิธีนี้ช่วยลดอิทธิพลของปัจจัยข้างต้น และทำให้ส่วนที่ช้าโดดเด่นขึ้นอย่างชัดเจน

วัดผล ปรับปรุง วัดผล

เมื่อพบจุดที่ทำงานช้าในโปรแกรม ให้ลองหาวิธีปรับปรุงลักษณะการทํางาน หลังจากเปลี่ยนรหัสแล้ว ให้สร้างโปรไฟล์อีกครั้ง หากพอใจกับผลลัพธ์ที่ได้ ก็ดำเนินการต่อได้เลย แต่หากไม่เห็นความคืบหน้า คุณควรเปลี่ยนกลับและอย่าปล่อยให้การเปลี่ยนแปลงนั้นอยู่ "เพราะไม่มีผลเสียใดๆ"

กลยุทธ์ด้านการเพิ่มประสิทธิภาพ

ลดการโต้ตอบ 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 สำหรับการอ่านและการเขียน โดยหลักการแล้ว คุณควรจัดกลุ่มโค้ดเป็น 2 ระยะเสมอ ดังนี้

  • ระยะที่ 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 สามารถช่วยเพิ่มประสิทธิภาพการแสดงผลได้อย่างมาก ในกรณีนี้ โค้ดต้นฉบับต้องคํานวณสไตล์และเลย์เอาต์หน้าเว็บ 2 ครั้งเพื่อสร้างผลลัพธ์เดียวกัน การเพิ่มประสิทธิภาพที่คล้ายกันนี้ใช้ได้กับโค้ด "ในชีวิตจริง" เกือบทั้งหมดและจะให้ผลลัพธ์ที่น่าทึ่ง

อ่านเพิ่มเติม: การแสดงผล: วาดภาพใหม่ จัดเรียงใหม่/จัดวางใหม่ เปลี่ยนสไตล์ใหม่ โดย Stoyan Stefanov

การวาดภาพใหม่และลูปเหตุการณ์

การดำเนินการ JavaScript ในเบราว์เซอร์จะเป็นไปตามรูปแบบ "Event Loop" โดยค่าเริ่มต้น เบราว์เซอร์จะอยู่ในสถานะ "ไม่มีการใช้งาน" สถานะนี้อาจถูกขัดจังหวะโดยเหตุการณ์จากการโต้ตอบของผู้ใช้ หรือสิ่งต่างๆ เช่น ตัวจับเวลา JavaScript หรือการเรียกกลับ Ajax เมื่อใดก็ตามที่ JavaScript ทำงานที่จุดหยุดชะงักดังกล่าว โดยปกติเบราว์เซอร์จะรอให้ JavaScript ทำงานเสร็จจนกว่าจะวาดหน้าจออีกครั้ง (อาจมีข้อยกเว้นสำหรับ JavaScript ที่ทำงานเป็นเวลานานมาก หรือในกรณีต่างๆ เช่น กล่องข้อความแจ้งที่ขัดจังหวะการทำงานของ JavaScript อย่างมีประสิทธิภาพ)

ผลที่ตามมา

  1. หากรอบการทำงานของภาพเคลื่อนไหว JavaScript ใช้เวลานานกว่า 1/30 วินาที คุณจะไม่สามารถสร้างภาพเคลื่อนไหวที่ราบรื่นได้ เนื่องจากเบราว์เซอร์จะไม่วาดภาพใหม่ระหว่างที่ JS ทำงาน เมื่อต้องจัดการเหตุการณ์ของผู้ใช้ด้วย คุณจะต้องทํางานได้เร็วขึ้นมาก
  2. บางครั้งการเลื่อนเวลาการดำเนินการ JavaScript บางอย่างออกไปสักครู่ก็มีประโยชน์ เช่น setTimeout(function() { ... }, 0) การดำเนินการนี้จะบอกให้เบราว์เซอร์เรียกใช้การเรียกกลับทันทีที่ลูปเหตุการณ์ไม่มีการใช้งานอีก (เบราว์เซอร์บางรุ่นจะรออย่างน้อย 10 มิลลิวินาที) โปรดทราบว่าการดำเนินการนี้จะสร้างรอบการเรียกใช้ JavaScript 2 รอบซึ่งอยู่ในช่วงเวลาใกล้เคียงกันมาก ทั้ง 2 กรณีอาจทริกเกอร์ให้หน้าจอวาดภาพใหม่ ซึ่งอาจทำให้เวลาโดยรวมที่ใช้วาดภาพเพิ่มขึ้นเป็น 2 เท่า การที่การเรียกใช้นี้จะทริกเกอร์การวาด 2 ครั้งหรือไม่นั้นขึ้นอยู่กับการประเมินแบบเฮuristic ในเบราว์เซอร์

เวอร์ชันปกติ

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)
}
หน่วงเวลา

เวอร์ชันที่เลื่อนเวลาแสดงให้เห็นว่าเบราว์เซอร์แสดงผล 2 ครั้ง แม้ว่าการเปลี่ยนแปลง 2 ครั้งในหน้าเว็บจะใช้เวลาเพียง 1/100 วินาที

การเริ่มต้นแบบ Lazy

ผู้ใช้ต้องการเว็บแอปที่โหลดได้เร็วและตอบสนองได้อย่างรวดเร็ว อย่างไรก็ตาม ผู้ใช้มีเกณฑ์ที่แตกต่างกันในสิ่งที่ตนเห็นว่าช้า ขึ้นอยู่กับการดําเนินการที่ตนทํา เช่น แอปไม่ควรทําการคํานวณจํานวนมากในเหตุการณ์การเลื่อนเมาส์ผ่าน เนื่องจากอาจทําให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่แย่ขณะที่ผู้ใช้เลื่อนเมาส์ต่อไป อย่างไรก็ตาม ผู้ใช้คุ้นเคยกับการรอสักครู่หลังจากคลิกปุ่ม

ดังนั้น คุณจึงควรย้ายโค้ดเริ่มต้นให้ทำงานช้าที่สุดเท่าที่จะทำได้ (เช่น เมื่อผู้ใช้คลิกปุ่มที่เปิดใช้งานคอมโพเนนต์หนึ่งๆ ของแอปพลิเคชัน)

ก่อน: 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 ใช้เวลานาน

คำแนะนำส่วนตัวของ Malte: อย่าทำอะไรใน $(document).ready โปรดส่งเอกสารในรูปแบบสุดท้าย โอเค คุณสามารถลงทะเบียน Listener เหตุการณ์ได้ แต่จะต้องใช้เฉพาะตัวเลือก id และ/หรือใช้การมอบหมายเหตุการณ์เท่านั้น สําหรับเหตุการณ์ที่มีค่าใช้จ่ายสูง เช่น "mousemove" ให้เลื่อนการลงทะเบียนไว้จนกว่าจะจําเป็น (เหตุการณ์ mouseover บนองค์ประกอบที่เกี่ยวข้อง)

และหากจําเป็นต้องดําเนินการบางอย่าง เช่น การส่งคําขอ 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 ซึ่งจะเพิ่มความเร็วของตัวเลือกในเบราว์เซอร์สมัยใหม่หลายเท่า

หากไม่ได้ผลหรือคุณต้องการให้เล่นได้อย่างรวดเร็วในเบราว์เซอร์สมัยใหม่ด้วย ให้ทำตามหลักเกณฑ์ต่อไปนี้

  • ระบุข้อมูลให้เฉพาะเจาะจงที่สุดทางด้านขวาของตัวเลือก
  • ใช้ชื่อแท็กที่คุณไม่ค่อยได้ใช้เป็นส่วนตัวเลือกด้านขวาสุด
  • หากวิธีข้างต้นไม่ได้ผล ให้ลองเขียนโค้ดใหม่เพื่อให้ใช้ตัวเลือก id ได้

การจัดการ DOM ทั้งหมดนี้ใช้เวลานาน

การแทรก ลบ และอัปเดตโหนด DOM จำนวนมากอาจทําให้ระบบทำงานช้ามาก โดยทั่วไปแล้ว การเพิ่มประสิทธิภาพนี้ทำได้ด้วยการสร้างสตริง HTML ขนาดใหญ่และใช้ domNode.innerHTML = newHTML แทนที่เนื้อหาเก่า โปรดทราบว่าการดำเนินการนี้อาจส่งผลเสียอย่างมากต่อความสามารถในการบำรุงรักษาและอาจสร้างลิงก์หน่วยความจำใน IE ดังนั้นโปรดระมัดระวัง

ปัญหาที่พบบ่อยอีกอย่างหนึ่งคือโค้ดเริ่มต้นอาจสร้าง HTML จำนวนมาก เช่น ปลั๊กอิน jQuery ที่เปลี่ยนช่องตัวเลือกให้กลายเป็น div หลายรายการเนื่องจากนักออกแบบต้องการแบบนั้นโดยไม่รู้แนวทางปฏิบัติแนะนำด้าน UX หากต้องการให้หน้าเว็บโหลดเร็วจริงๆ อย่าทำเช่นนั้น แต่ให้ส่งมาร์กอัปทั้งหมดจากฝั่งเซิร์ฟเวอร์ในรูปแบบสุดท้ายแทน การดำเนินการนี้ก็มีปัญหาหลายอย่างเช่นกัน ดังนั้นโปรดพิจารณาอย่างถี่ถ้วนว่าความเร็วนั้นคุ้มค่ากับการแลกหรือไม่

เครื่องมือ

  1. JSPerf - ทดสอบประสิทธิภาพข้อมูลโค้ด JavaScript สั้นๆ
  2. Firebug - สําหรับการจัดเก็บข้อมูลโปรไฟล์ใน Firefox
  3. เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ของ Google Chrome (มีให้บริการเป็น WebInspector ใน Safari)
  4. DOM Monster - สําหรับการเพิ่มประสิทธิภาพ DOM
  5. DynaTrace Ajax Edition - สําหรับโปรไฟล์และการเพิ่มประสิทธิภาพการวาดใน Internet Explorer

อ่านเพิ่มเติม

  1. Google Speed
  2. Paul Irish เกี่ยวกับประสิทธิภาพของ jQuery
  3. ประสิทธิภาพ JavaScript ขั้นสุด (ชุดสไลด์)