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

เกริ่นนำ

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

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

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

กลยุทธ์

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

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

รายละเอียดเสมือนจริง++ ด้วย HTML5

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

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

GPU สามารถเร่งการทำงานของด้านเหล่านี้ของเอกสารได้

  • การจัดองค์ประกอบเลย์เอาต์ทั่วไป
  • การเปลี่ยน CSS3
  • การแปลงแบบ 3 มิติ CSS3
  • ภาพวาดบนผืนผ้าใบ
  • ภาพวาด 3 มิติ WebGL

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

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

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

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

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

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

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

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

Safari มีแฟล็กรันไทม์ที่คล้ายกันตามที่อธิบายไว้ที่นี่

การเปลี่ยนของ CSS3

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

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

ปัจจุบันไลบรารีหลายแห่งได้เปิดตัว API ภาพเคลื่อนไหวที่ใช้ประโยชน์จากการเปลี่ยน หากมี หรือไม่ก็กลับไปใช้ภาพเคลื่อนไหวรูปแบบ DOM มาตรฐาน scripty2, การเปลี่ยน YUI, jQuery สร้างภาพเคลื่อนไหวที่ปรับปรุงแล้ว

แปลภาษา CSS3

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

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

window.requestAnimationFrame

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

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

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

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

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

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

เวลาดำเนินการรวมของฟังก์ชันคือเวลาโดยรวมที่ใช้ในการดำเนินการจากบนลงล่าง เวลาดำเนินการสุทธิคือเวลาดำเนินการรวมลบด้วยเวลาที่ใช้ในการเรียกใช้ฟังก์ชันที่เรียกจากฟังก์ชัน

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

โปรดดูรายละเอียดเพิ่มเติมในเอกสาร Chrome Dev Tools เกี่ยวกับการสร้างโปรไฟล์

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 รองรับนิพจน์ของฟังก์ชันการตั้งชื่อ การทำเช่นนี้จะทำให้โปรไฟล์ปรากฏในเครื่องมือสร้างโปรไฟล์อย่างสมบูรณ์ เกิดปัญหากับโซลูชันนี้ กล่าวคือ นิพจน์ที่มีชื่อจะใส่ชื่อฟังก์ชันไว้ในขอบเขตคำศัพท์ปัจจุบัน เพราะอาจทำให้เกิดการเปลี่ยนแปลงสัญลักษณ์อื่นๆ ได้ ดังนั้นโปรดระวัง

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

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

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

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

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

มุมมองไทม์ไลน์สามารถสร้างข้อมูลปริมาณมหาศาล คุณจึงควรสร้างกรอบการทดสอบให้น้อยที่สุดซึ่งสามารถดำเนินการได้อย่างอิสระ

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

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

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

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

ระบุแง่มุมเดียว

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

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

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

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

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

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

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

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

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 เหตุการณ์ได้ แต่ใช้เพียงตัวเลือกรหัสและ/หรือใช้การมอบสิทธิ์เหตุการณ์เท่านั้น สําหรับเหตุการณ์ราคาแพง เช่น "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 เพื่อแทนที่เนื้อหาเดิม โปรดทราบว่านี่อาจไม่ดีต่อความสามารถในการบำรุงรักษาและอาจสร้างลิงก์หน่วยความจำใน 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
  2. Paul Ireland เกี่ยวกับประสิทธิภาพของ jQuery
  3. ประสิทธิภาพ JavaScript ระดับสูงสุด (ชุดสไลด์)