เกี่ยวกับคำอธิบายของตัวบ่งชี้ประสิทธิภาพส่วนหน้า อุตสาหกรรมมีความคิดเห็นเป็นของตัวเอง โดยสรุป มันเกี่ยวข้องกับประสิทธิภาพหน้าจอแรกและความคล่องของหน้า คราวนี้เราจะปรับประสิทธิภาพการโต้ตอบของหน้าให้เหมาะสม มุมมองของการวิเคราะห์ความคล่องแคล่วของหน้า [คำแนะนำบทช่วยสอนที่เกี่ยวข้อง: "บทช่วยสอนเชิงมุม"]
ความคล่องแคล่วของหน้าคืออะไร
ความคล่องแคล่วของหน้าถูกกำหนดโดยอัตราเฟรม FPS (เฟรมต่อวินาที - เฟรมที่ส่งต่อวินาที) โดยทั่วไป อัตรารีเฟรชหน้าจอของเบราว์เซอร์หลักคือ 60Hz (รีเฟรช 60 ครั้งต่อวินาที) และอัตราเฟรมที่เหมาะสมที่สุดคือ 60 FPS อัตราเฟรมยิ่งหน้าเรียบเนียนขึ้น 60Hz หมายความว่าหน้าจอแสดงผลจะรีเฟรชทุกๆ 16.6ms ซึ่งหมายความว่าแต่ละหน้าเรนเดอร์จะต้องเสร็จสิ้นภายใน 16.6ms มิฉะนั้นหน้าจะสูญเสียเฟรมและค้าง根因在于:浏览器中的JavaScript 执行和页面渲染会相互阻塞
ใน devtools ของ Chrome เราสามารถดำเนินการ Cmd+Shift+P และป้อน show fps เพื่อเปิดแผง fps อย่างรวดเร็ว ดังแสดงในรูปต่อไปนี้:
ด้วยการสังเกตแผง FPS เราสามารถตรวจสอบความคล่องของหน้าปัจจุบันได้อย่างง่ายดาย
1 ปัจจัยที่ส่งผลต่อประสิทธิภาพของเพจ
การโต้ตอบของเพจจะราบรื่นหรือไม่นั้นขึ้นอยู่กับว่าการตอบสนองของเพจนั้นราบรื่นหรือไม่ และ การตอบสนองของเพจนั้น เป็นกระบวนการในการแสดงผลการเปลี่ยนแปลงสถานะของเพจไปยังเพจนั้นอีกครั้ง
กระบวนการตอบกลับเพจมีดังนี้:
โดยทั่วไป ตรรกะการประมวลผลเหตุการณ์ตัวจัดการเหตุการณ์ใช้เวลาไม่นานเกินไป ดังนั้นปัจจัยที่ส่งผลต่อประสิทธิภาพของ Angular ส่วนใหญ่จะอยู่ที่异步事件触发
และ变更检测
โดยทั่วไป ตรรกะการประมวลผลเหตุการณ์ตัวจัดการเหตุการณ์ใช้เวลาไม่นานเกินไป ดังนั้นปัจจัยที่ส่งผลต่อประสิทธิภาพของ Angular ส่วนใหญ่จะอยู่ที่การทริกเกอร์เหตุการณ์แบบอะซิงโครนัสและการตรวจจับการเปลี่ยนแปลง
สำหรับ Angular กระบวนการเรนเดอร์เพจเป็นกระบวนการตรวจจับการเปลี่ยนแปลง เป็นที่เข้าใจได้ว่าการตรวจจับการเปลี่ยนแปลงของ Angular จะต้องเสร็จสิ้นภายใน 16.6 มิลลิวินาที เพื่อหลีกเลี่ยงการสูญเสียเฟรมของหน้าและความล่าช้า
ประสิทธิภาพการตอบกลับเพจสามารถปรับให้เหมาะสมได้จากสามด้านต่อไปนี้
(1) สำหรับระยะเหตุการณ์ทริกเกอร์ การทริกเกอร์เหตุการณ์แบบอะซิงโครนัสสามารถลดลงได้ เพื่อลดจำนวนการตรวจจับการเปลี่ยนแปลงและการเรนเดอร์โดยรวม
(2) สำหรับขั้นตอนลอจิกการดำเนินการตัวจัดการเหตุการณ์ เวลาการดำเนินการสามารถลดลงได้โดยการปรับความซับซ้อนให้เหมาะสม ตรรกะของโค้ด
(3) สำหรับการเชื่อมโยงข้อมูลการตรวจจับการเปลี่ยนแปลงและขั้นตอนการอัปเดต DOM สามารถลด จำนวนการคำนวณ การตรวจจับการเปลี่ยนแปลงและข้อมูลเทมเพลตเพื่อลดเวลาในการเรนเดอร์
สำหรับ (2) ตัวจัดการเหตุการณ์ จำเป็นต้องวิเคราะห์ปัญหาเฉพาะ รายละเอียดและจะไม่กล่าวถึง ส่วนใหญ่จะเน้นไปที่ (1) (3) ) การเพิ่มประสิทธิภาพ
2 แผนการเพิ่มประสิทธิภาพ
2.1 ลดเหตุการณ์อะซิงโครนัสที่ทริก
เกอร์แองกูลาร์ ในโหมดการตรวจจับการเปลี่ยนแปลงเริ่มต้น เหตุการณ์อะซิงโครนัสจะทริกเกอร์การตรวจจับการเปลี่ยนแปลงทั่วโลก ดังนั้น ลดการทริกเกอร์ของเหตุการณ์อะซิงโครนัส จะปรับปรุงประสิทธิภาพของ Angular ได้อย่างมาก
เหตุการณ์แบบอะซิงโครนัสรวมถึงเหตุการณ์งานแมโครและเหตุการณ์งานไมโคร
การเพิ่มประสิทธิภาพของเหตุการณ์อะซิงโครนัสมีไว้สำหรับเหตุการณ์การฟังเอกสารเป็นหลัก ตัวอย่างเช่น ฟังการคลิก การเลื่อนเมาส์ การเลื่อนเมาส์... และกิจกรรมอื่นๆ ในเอกสาร
สถานการณ์เหตุการณ์การฟัง:
Renderer2.listen(document,…)
fromEvent(document,…)
document.addEventListener(…)
เหตุการณ์การฟัง DOM จะต้องถูกลบออก เมื่อไม่จำเป็นต้องถูกทริกเกอร์
ตัวอย่างเช่น:
สถานการณ์การใช้คำสั่งกล่องข้อความ [ป๊อป]: การกรองคอลัมน์ในตาราง การคลิกที่อื่นที่ไม่ใช่ไอคอน หรือการเลื่อนหน้า การซ่อนกล่องป๊อปอัปตัวกรองคอลัมน์
วิธีก่อนหน้านี้คือการตรวจสอบเหตุการณ์การคลิกและเหตุการณ์การเลื่อนโดยตรง ของเอกสารในคำสั่ง pop วิธีนี้: ข้อเสียเปรียบเพียงอย่างเดียวคือกล่องพร้อมท์ไม่แสดง แต่ยังคงมีเหตุการณ์การตรวจสอบซึ่งไม่สมเหตุสมผลมาก
วิธีแก้ปัญหาที่สมเหตุสมผล: ฟังเฉพาะเหตุการณ์การคลิกและเลื่อนเมื่อกล่องพร้อมท์ปรากฏขึ้น และลบเหตุการณ์การฟังเมื่อซ่อนไว้
สำหรับเหตุการณ์การฟัง DOM ที่ถูกทริกเกอร์บ่อยครั้ง คุณสามารถใช้ตัวดำเนินการ rjx เพื่อปรับกิจกรรมให้เหมาะสมได้ ดูตัวดำเนินการ Rjx สำหรับรายละเอียด RxJS มาร์เบิลส์
2.2 การตรวจจับการเปลี่ยนแปลง
การตรวจจับการเปลี่ยนแปลงคืออะไร?
เพื่อทำความเข้าใจการตรวจจับการเปลี่ยนแปลง เราสามารถค้นหาคำตอบจากเป้าหมายของการตรวจจับการเปลี่ยนแปลงได้ เป้าหมายของการตรวจจับการเปลี่ยนแปลงเชิงมุมคือการทำให้โมเดล (โค้ด TypeScript) และเทมเพลต (HTML) ซิงค์กัน ดังนั้น การตรวจจับการเปลี่ยนแปลงจึงเข้าใจได้ดังนี้: การตรวจจับการเปลี่ยนแปลงโมเดลและการอัปเดตเทมเพลต ( DOM ) ในเวลา เดียวกัน
กระบวนการตรวจจับการเปลี่ยนแปลงคืออะไร?
โดยการดำเนินการตรวจหาการเปลี่ยนแปลงตามลำดับ จากบนลงล่าง ในแผนผังส่วนประกอบ นั่นคือ การตรวจจับการเปลี่ยนแปลงจะดำเนินการในส่วนประกอบหลักก่อน จากนั้นจึงดำเนินการในส่วนประกอบย่อย ขั้นแรกให้ตรวจสอบการเปลี่ยนแปลงข้อมูลขององค์ประกอบหลัก จากนั้นอัปเดตเทมเพลตองค์ประกอบหลัก เมื่อพบองค์ประกอบย่อย ระบบจะอัปเดตค่าที่เชื่อมโยงกับองค์ประกอบย่อย จากนั้นป้อนองค์ประกอบย่อยเพื่อดูว่า ดัชนีของค่าอินพุต @Input มีการเปลี่ยนแปลง หากมีการเปลี่ยนแปลง ให้ทำเครื่องหมายองค์ประกอบย่อยว่าสกปรก ซึ่งต้องมีการตรวจจับการเปลี่ยนแปลงในภายหลัง ให้อัปเดตเทมเพลตด้านหลังองค์ประกอบย่อยในองค์ประกอบหลักต่อไป ได้รับการอัปเดตแล้ว ให้ทำการเปลี่ยนแปลงการตรวจจับองค์ประกอบย่อย
2.2.1 หลักการของการตรวจจับการเปลี่ยนแปลงเชิงมุม
ในโหมดเริ่มต้นการตรวจจับการเปลี่ยนแปลงเริ่มต้น หลักการของเหตุการณ์อะซิงโครนัสที่ทริกเกอร์การตรวจจับการเปลี่ยนแปลงของ Angular คือเชิงมุมจะเรียกเมธอดติ๊ก () ของ ApplicationRef เมื่อประมวลผลเหตุการณ์อะซิงโครนัสโดยใช้ Zone.js เพื่อดำเนินการจากองค์ประกอบรูท ไปยังองค์ประกอบย่อย ในระหว่างกระบวนการเริ่มต้นของแอปพลิเคชัน Angular โซน (NgZone) จะถูกสร้างอินสแตนซ์ จากนั้นตรรกะทั้งหมดจะถูกรันในอ็อบเจ็กต์ _inner ของอ็อบเจ็กต์
Zone.js ใช้คลาสต่อไปนี้:
หลักการของกระบวนการตรวจจับมีดังนี้:
การทำงานของผู้ใช้ทริกเกอร์เหตุการณ์แบบอะซิงโครนัส (เช่น: เหตุการณ์ DOM, คำขออินเทอร์เฟซ...)
=> คลาส ZoneTask จัดการเหตุการณ์ เมธอด runTask() ของโซนถูกเรียกในฟังก์ชัน invokeTask() ในเมธอด runTask โซนจะส่งผ่านแอตทริบิวต์อินสแตนซ์ _zoneDelegate และเรียก hook ของ ZoneSpec
=> สาม hook ของ ZoneSpec (onInvoidTask, onInrigg, onHasTask) ผ่าน checkStable () ฟังก์ชัน Trigger Zone.onMicrotaskEmpty.emit(null) การแจ้งเตือน
=> คอมโพเนนต์รูทจะเรียกเครื่องหมายถูก () หลังจากฟัง onMicrotaskEmpty และเรียก detectChanges()
ในเมธอดติ๊กเพื่อเริ่มการตรวจจับจากส่วนประกอบรูท
=> ··· refreshView()
เรียก executeTemplate()
ในเมธอด executeTemplate
เรียก templateFn()
เพื่ออัพเดตค่าที่ผูกกับเทมเพลตและส่วนประกอบย่อย (这时候会去检测子组件的
输入引用是否改变,如果有改变,会将子组件标记为
Dirty ,也就是该子组件需要变更检测
)
แผนภูมิลำดับหลักการตรวจจับการเปลี่ยนแปลงโดยละเอียด:
ลดความซับซ้อนของกระบวนการ:
ทริกเกอร์เหตุการณ์อะซิงโครนัส
=> ZoneTask จัดการเหตุการณ์
=> ZoneDelegate เรียกตะขอ ZoneSpec เพื่อทริกเกอร์การแจ้งเตือน onMicrotaskEmpty
=> ส่วนประกอบรูทได้รับการแจ้งเตือน onMicrotaskEmpty ดำเนินการติ๊ก () และเริ่มการตรวจจับและอัปเดต dom
ดังที่เห็นได้จากโค้ดด้านบน当微任务为空的时候才会触发变更检测
แผนภูมิการไหลหลักการตรวจจับการเปลี่ยนแปลงอย่างง่าย:
บล็อกอ้างอิงการวิเคราะห์ซอร์สโค้ดเชิงมุม Zone.js
2.2.2 โซลูชันการเพิ่มประสิทธิภาพการตรวจจับการเปลี่ยนแปลง
1) ใช้หลักการของโหมด OnPush
: ลดการเสียเวลาในการตรวจจับการเปลี่ยนแปลงหนึ่งครั้ง
ความแตกต่างระหว่างโหมด OnPush และโหมดเริ่มต้นคือเหตุการณ์การฟัง DOM เหตุการณ์ตัวจับเวลา และสัญญาจะไม่ทริกเกอร์การตรวจจับการเปลี่ยนแปลง สถานะของส่วนประกอบในโหมดเริ่มต้นจะเป็น CheckAlways เสมอ ซึ่งหมายความว่าส่วนประกอบจะต้องได้รับการทดสอบทุกรอบการตรวจจับ
ในโหมด OnPush: สถานการณ์ต่อไปนี้จะทริกเกอร์การตรวจจับการเปลี่ยนแปลง
S1 และการอ้างอิง @Input ของส่วนประกอบที่จะเปลี่ยนแปลง
S2 เหตุการณ์ที่เชื่อมโยงกับ DOM ของส่วนประกอบ รวมถึงเหตุการณ์ที่เชื่อมโยงกับ DOM ของส่วนประกอบย่อย เช่น การคลิก ส่ง และเลื่อนเมาส์ลง @HostListener()
หมายเหตุ:
เหตุการณ์ DOM ที่ถูกตรวจสอบผ่าน renderer2.listen() จะไม่ทริกเกอร์การตรวจจับการเปลี่ยนแปลง
วิธีการฟังดั้งเดิมของ dom.addEventListener() จะไม่ทริกเกอร์การตรวจจับการเปลี่ยนแปลง
เช่นกัน ในเวลาเดียวกัน
S4 ใช้วิธีการต่อไปนี้เพื่อทริกเกอร์การตรวจจับการเปลี่ยนแปลงด้วยตนเอง:
ChangeDetectorRef.detectChanges(): การตรวจจับการเปลี่ยนแปลงทริกเกอร์สำหรับส่วนประกอบปัจจุบันและส่วนประกอบย่อยที่ไม่ใช่ OnPush
ChangeDetectorRef.markForCheck(): ทำเครื่องหมายมุมมองปัจจุบันและบรรพบุรุษทั้งหมดว่าสกปรก และการตรวจจับจะถูกทริกเกอร์ในรอบการตรวจจับถัดไป
ApplicationRef.tick(): จะไม่ทริกเกอร์การตรวจจับการเปลี่ยนแปลง
2)
หลักการใช้ NgZone.runOutsideAngular(): ลดจำนวนการตรวจจับการเปลี่ยนแปลง
และเขียนการตรวจสอบเหตุการณ์ Dom ทั่วโลกในการเรียกกลับของเมธอด NgZone.runOutsideAngular() ไม่กระตุ้นการตรวจจับการเปลี่ยนแปลง หากคอมโพเนนต์ปัจจุบันไม่ได้รับการอัพเดต คุณสามารถเรียกใช้งานฮุกของ detectorChanges() ของ ChangeDetectorRef ในฟังก์ชันเรียกกลับเพื่อทริกเกอร์การตรวจจับการเปลี่ยนแปลงของคอมโพเนนต์ปัจจุบันด้วยตนเอง
ตัวอย่าง: ส่วนประกอบไอคอนแบบไดนามิกของ app-icon-react
2.2.3 วิธีการดีบัก
1: คุณสามารถใช้ปลั๊กอิน Angular DevTools ในคอนโซลของเบราว์เซอร์เพื่อดูเหตุการณ์ DOM บางอย่างและสถานะการตรวจจับเชิงมุมได้:
วิธีที่ 2: คุณสามารถป้อน: ng.profiler.timeChangeDetection() ในคอนโซลได้โดยตรงเพื่อดูเวลาการตรวจจับ วิธีนี้จะสามารถดูเวลาการตรวจจับทั่วโลกได้ บล็อกอ้างอิง การทำโปรไฟล์ การตรวจจับการเปลี่ยนแปลงเชิงมุม
2.3 การเพิ่มประสิทธิภาพเทมเพลต (HTML)
2.3.1 ลดการแสดงผล DOM: ngFor plus trackBy
การใช้แอตทริบิวต์ trackBy ของ *ngFor นั้น Angular จะเปลี่ยนแปลงเฉพาะและเรนเดอร์รายการที่เปลี่ยนแปลงอีกครั้งโดยไม่ต้องโหลดรายการรายการใหม่ทั้งหมด
ตัวอย่างเช่น: สถานการณ์จำลองการเรียงลำดับตาราง หากมีการเพิ่ม trackBy ลงใน ngFor เฉพาะองค์ประกอบ Dom ของแถวเท่านั้นที่จะถูกย้ายเมื่อมีการแสดงผลตาราง หากไม่ได้เพิ่ม TrackBy องค์ประกอบ Dom ของตารางที่มีอยู่จะถูกลบออกก่อน จากนั้นองค์ประกอบ Dom ของแถวจะถูกเพิ่ม แน่นอนว่าประสิทธิภาพของการย้ายเฉพาะองค์ประกอบ dom จะดีกว่ามาก
2.3.2 ห้ามใช้ฟังก์ชันในนิพจน์เทมเพลต
ห้ามใช้การเรียกฟังก์ชันในนิพจน์เทมเพลตเชิงมุม คุณสามารถใช้ไปป์แทน หรือคุณสามารถใช้ตัวแปรหลังจากการคำนวณด้วยตนเอง เมื่อใช้ฟังก์ชันในเทมเพลต ไม่ว่าค่าจะเปลี่ยนแปลงหรือไม่ก็ตาม ฟังก์ชันจะถูกดำเนินการทุกครั้งที่ดำเนินการตรวจจับการเปลี่ยนแปลง ซึ่งจะส่งผลต่อประสิทธิภาพ
สถานการณ์การใช้ฟังก์ชันในเทมเพลต:
2.3.3 ลดการใช้ ngFor
การใช้ ngFor จะส่งผลต่อประสิทธิภาพเมื่อมีข้อมูลจำนวนมาก
ตัวอย่าง:
การใช้ ngFor:
ไม่ได้ใช้ ngFor: ประสิทธิภาพดีขึ้นประมาณ 10 เท่า