ใน JavaScript เราควรใช้ตัวแปรท้องถิ่นแทนตัวแปรทั่วโลกให้มากที่สุด ทุกคนรู้จักประโยคนี้ แต่ใครเป็นคนพูดก่อน? ทำไมทำเช่นนี้? มีพื้นฐานสำหรับเรื่องนี้หรือไม่? หากไม่ทำเช่นนี้ จะสูญเสียประสิทธิภาพไปเท่าใด บทความนี้จะสำรวจคำตอบสำหรับคำถามเหล่านี้และทำความเข้าใจโดยพื้นฐานว่าปัจจัยใดบ้างที่เกี่ยวข้องกับประสิทธิภาพการอ่านและการเขียนของตัวแปร
【ต้นฉบับ】ประสิทธิภาพตัวแปรจาวาสคริปต์
【ผู้แต่ง】นิโคลัส ซี. ซาคัส
[การแปล] ใน JavaScript เหตุใดเราจึงควรใช้ตัวแปรท้องถิ่นทุกครั้งที่เป็นไปได้
[นักแปล] Mingda
ต่อไปนี้เป็นการแปลข้อความต้นฉบับ:
ในประเด็นวิธีปรับปรุงประสิทธิภาพของ JavaScript คำแนะนำที่ได้ยินบ่อยที่สุดคือการใช้ตัวแปรท้องถิ่นแทนตัวแปรส่วนกลาง นี่เป็นคำแนะนำที่ติดอยู่กับฉันและไม่เคยตั้งคำถามเลยตลอดเก้าปีที่ฉันทำงานด้านการพัฒนาเว็บ และคำแนะนำนี้อิงจากการจัดการขอบเขตและการแก้ไขตัวระบุของ JavaScript
ก่อนอื่น เราต้องทำให้ชัดเจนว่าฟังก์ชันต่างๆ ถูกรวมไว้เป็นวัตถุใน JavaScript จริงๆ แล้วกระบวนการสร้างฟังก์ชันคือกระบวนการสร้างวัตถุ แต่ละออบเจ็กต์ฟังก์ชันมีคุณสมบัติภายในที่เรียกว่า [[ขอบเขต]] ซึ่งมีข้อมูลขอบเขตเมื่อสร้างฟังก์ชัน อันที่จริง คุณลักษณะ [[ขอบเขต]] สอดคล้องกับรายการของวัตถุ (วัตถุตัวแปร) และสามารถเข้าถึงวัตถุในรายการได้จากภายในฟังก์ชัน ตัวอย่างเช่น ถ้าเราสร้างฟังก์ชันส่วนกลาง A ดังนั้นคุณสมบัติภายใน [[Scope]] ของ A จะมีอ็อบเจ็กต์โกลบอลเพียงอันเดียว (Global Object) และถ้าเราสร้างฟังก์ชัน B ใหม่ใน A ดังนั้นแอตทริบิวต์ [[Scope] ] ของ B จะมี วัตถุสองชิ้น วัตถุ Activation Object ของฟังก์ชัน A อยู่ด้านหน้า และวัตถุส่วนกลาง (วัตถุส่วนกลาง) อยู่ด้านหลัง
เมื่อฟังก์ชันถูกดำเนินการ ออบเจ็กต์ที่เรียกใช้งานได้ (Execution Object) จะถูกสร้างขึ้นโดยอัตโนมัติและเชื่อมโยงกับขอบเขตเชน (Scope Chain) ห่วงโซ่ขอบเขตจะถูกสร้างขึ้นผ่านสองขั้นตอนต่อไปนี้สำหรับการแก้ไขตัวระบุ
1. ขั้นแรก คัดลอกออบเจ็กต์ในคุณสมบัติภายในของออบเจ็กต์ฟังก์ชัน [[ขอบเขต]] ไปยังห่วงโซ่ขอบเขตตามลำดับ
2. ประการที่สอง เมื่อฟังก์ชันถูกดำเนินการ ออบเจ็กต์การเปิดใช้งานใหม่จะถูกสร้างขึ้น โดยมีคำจำกัดความของสิ่งนี้ พารามิเตอร์ (อาร์กิวเมนต์) และตัวแปรท้องถิ่น (รวมถึงพารามิเตอร์ที่มีชื่อ) . ด้านหน้าของโดเมนเชน
ในระหว่างการดำเนินการโค้ด JavaScript เมื่อพบตัวระบุ จะถูกค้นหาในห่วงโซ่ขอบเขตของบริบทการดำเนินการ (บริบทการดำเนินการ) ตามชื่อของตัวระบุ เริ่มต้นจากออบเจ็กต์แรกในสายขอบเขต (ออบเจ็กต์การเปิดใช้งานของฟังก์ชัน) หากไม่พบ ให้ค้นหาออบเจ็กต์ถัดไปในสายขอบเขต และอื่นๆ จนกว่าจะพบคำจำกัดความของตัวระบุ หากไม่พบออบเจ็กต์สุดท้ายในขอบเขตซึ่งก็คือออบเจ็กต์ส่วนกลางหลังจากการค้นหา ข้อผิดพลาดจะเกิดขึ้น แจ้งให้ผู้ใช้ทราบว่าตัวแปรไม่ได้ถูกกำหนดไว้ นี่คือรูปแบบการดำเนินการฟังก์ชันและกระบวนการแก้ไขตัวระบุ (การแก้ปัญหาตัวระบุ) ที่อธิบายไว้ในมาตรฐาน ECMA-262 ปรากฎว่าเอ็นจิ้น JavaScript ส่วนใหญ่ใช้งานในลักษณะนี้ ควรสังเกตว่า ECMA-262 ไม่ได้กำหนดการใช้โครงสร้างนี้ แต่อธิบายเฉพาะฟังก์ชันส่วนนี้เท่านั้น
หลังจากเข้าใจกระบวนการแก้ไขตัวระบุ (Identifier Resolution) แล้ว เราก็เข้าใจได้ว่าเหตุใดตัวแปรท้องถิ่นจึงได้รับการแก้ไขเร็วกว่าตัวแปรในขอบเขตอื่นๆ เนื่องจากกระบวนการค้นหาสั้นลงอย่างมาก แต่จะเร็วขนาดไหนล่ะ? เพื่อตอบคำถามนี้ ฉันจำลองชุดการทดสอบเพื่อทดสอบประสิทธิภาพของตัวแปรที่ความลึกของขอบเขตที่แตกต่างกัน
การทดสอบครั้งแรกคือการเขียนค่าที่ง่ายที่สุดให้กับตัวแปร (ใช้ค่าตัวอักษร 1 ในที่นี้) ผลลัพธ์จะแสดงในรูปด้านล่าง ซึ่งน่าสนใจมาก:
จากผลลัพธ์จะเห็นได้ไม่ยากว่าเมื่อกระบวนการแยกวิเคราะห์ตัวระบุจำเป็นต้องมีการค้นหาเชิงลึก ประสิทธิภาพจะสูญเสียไป และระดับของการสูญเสียประสิทธิภาพจะเพิ่มขึ้นตามความลึกของตัวระบุที่เพิ่มขึ้น ไม่น่าแปลกใจเลยที่ Internet Explorer ทำงานได้แย่ที่สุด (แต่พูดตามตรง มีการปรับปรุงบางอย่างใน IE 8) เป็นที่น่าสังเกตว่ามีข้อยกเว้นบางประการที่นี่ Google Chrome และ WebKit เวอร์ชันเที่ยงคืนล่าสุดมีเวลาเข้าถึงตัวแปรที่เสถียรมาก และไม่เพิ่มขึ้นตามความลึกของขอบเขตที่เพิ่มขึ้น แน่นอนว่าสิ่งนี้น่าจะมาจากเอ็นจิ้น JavaScript รุ่นต่อไปที่พวกเขาใช้ นั่นคือ V8 และ SquirrelFish เอ็นจิ้นเหล่านี้ทำการปรับให้เหมาะสมเมื่อรันโค้ด และเป็นที่ชัดเจนว่าการปรับให้เหมาะสมเหล่านี้ทำให้การเข้าถึงตัวแปรเร็วขึ้นกว่าที่เคย Opera ยังทำงานได้ดี โดยเร็วกว่า IE, Firefox และ Safari เวอร์ชันปัจจุบันมาก แต่ช้ากว่าเบราว์เซอร์ที่ใช้ V8 และ Squirrelfish ประสิทธิภาพของ Firefox 3.1 Beta 2 ค่อนข้างจะคาดไม่ถึง ประสิทธิภาพการดำเนินการของตัวแปรในเครื่องนั้นสูงมาก แต่เมื่อจำนวนเลเยอร์ขอบเขตเพิ่มขึ้น ประสิทธิภาพก็จะลดลงอย่างมาก ควรสังเกตว่าฉันใช้การตั้งค่าเริ่มต้นที่นี่ ซึ่งหมายความว่า Firefox ไม่ได้เปิดฟังก์ชัน Trace ไว้
ผลลัพธ์ข้างต้นได้มาจากการดำเนินการเขียนกับตัวแปร จริงๆ แล้ว ฉันอยากรู้ว่าสถานการณ์จะแตกต่างออกไปหรือไม่เมื่ออ่านตัวแปร ดังนั้นฉันจึงทำการทดสอบต่อไปนี้ พบว่าความเร็วในการอ่านเร็วกว่าความเร็วในการเขียนเล็กน้อย แต่แนวโน้มการเปลี่ยนแปลงประสิทธิภาพมีความสม่ำเสมอ
เช่นเดียวกับในการทดสอบครั้งก่อน Internet Explorer และ Firefox ยังคงช้าที่สุด และ Opera ก็แสดงประสิทธิภาพที่สะดุดตามาก ในทำนองเดียวกัน Chrome และ Webkit Midnight Edition เวอร์ชันล่าสุดก็แสดงแนวโน้มประสิทธิภาพที่ไม่เกี่ยวข้องกับความลึกของขอบเขตเช่นกัน คุ้มค่าที่จะให้ความสนใจ ใช่ เวลาในการเข้าถึงตัวแปรใน Firefox 3.1 Beta 2 ยังคงมีการก้าวกระโดดที่แปลกประหลาด
ในระหว่างการทดสอบ ฉันค้นพบปรากฏการณ์ที่น่าสนใจ ซึ่งก็คือ Chrome จะสูญเสียประสิทธิภาพเพิ่มเติมเมื่อเข้าถึงตัวแปรส่วนกลาง เวลาในการเข้าถึงตัวแปรส่วนกลางไม่เกี่ยวข้องกับระดับขอบเขต แต่จะนานกว่าเวลาในการเข้าถึงตัวแปรภายในเครื่องในระดับเดียวกันถึง 50%
การทดสอบทั้งสองนี้ทำให้เรารู้แจ้งอะไรได้บ้าง ประการแรกคือการตรวจสอบมุมมองเก่า ซึ่งก็คือการใช้ตัวแปรท้องถิ่นให้มากที่สุด ในเบราว์เซอร์ทั้งหมด การเข้าถึงตัวแปรภายในเครื่องจะเร็วกว่าการเข้าถึงตัวแปรข้ามขอบเขต รวมถึงตัวแปรส่วนกลางด้วย ประเด็นต่อไปนี้ควรเป็นประสบการณ์ที่ได้รับผ่านการทดสอบนี้:
* ตรวจสอบตัวแปรทั้งหมดที่ใช้ในฟังก์ชันอย่างระมัดระวัง หากมีตัวแปรที่ไม่ได้กำหนดไว้ในขอบเขตปัจจุบันและมีการใช้มากกว่าหนึ่งครั้ง เราควรบันทึกตัวแปรนี้ไว้ใน ตัวแปรท้องถิ่น ให้ใช้ตัวแปรท้องถิ่นนี้เพื่อดำเนินการอ่านและเขียน วิธีนี้สามารถช่วยให้เราลดความลึกในการค้นหาของตัวแปรนอกขอบเขตเหลือ 1 ได้ ซึ่งมีความสำคัญเป็นพิเศษสำหรับตัวแปรส่วนกลาง เนื่องจากตัวแปรส่วนกลางจะถูกค้นหาที่ตำแหน่งสุดท้ายของห่วงโซ่ขอบเขตเสมอ
* หลีกเลี่ยงการใช้คำสั่ง with เนื่องจากมันจะปรับเปลี่ยนขอบเขตขอบเขตของบริบทการดำเนินการ (บริบทการดำเนินการ) และเพิ่มวัตถุ (วัตถุตัวแปร) ที่ด้านหน้า ซึ่งหมายความว่าในระหว่างการดำเนินการ with ตัวแปรท้องถิ่นจริงจะถูกย้ายไปยังตำแหน่งที่สองบนห่วงโซ่ขอบเขต ซึ่งจะทำให้ประสิทธิภาพลดลง
* หากคุณแน่ใจว่าโค้ดชิ้นหนึ่งจะส่งข้อยกเว้นอย่างแน่นอน ให้หลีกเลี่ยงการใช้ try-catch เนื่องจากสาขา catch จะถูกประมวลผลในห่วงโซ่ขอบเขตเดียวกันกับด้วย อย่างไรก็ตาม ไม่มีการสูญเสียประสิทธิภาพในโค้ดสาขา try ดังนั้นจึงยังคงแนะนำให้ใช้ try-catch เพื่อตรวจจับข้อผิดพลาดที่คาดเดาไม่ได้
หากคุณต้องการพูดคุยเพิ่มเติมเกี่ยวกับหัวข้อนี้ ฉันได้พูดคุยเล็กน้อยที่ Mountain View JavaScript Meetup เมื่อเดือนที่แล้ว คุณสามารถดาวน์โหลดสไลด์บน SlideShare หรือชมวิดีโอเต็มของงานปาร์ตี้ ซึ่งเริ่มประมาณนาทีที่ 11 ของการบรรยายของฉัน
หมายเหตุผู้แปล:
หากคุณมีข้อสงสัยใดๆ ในขณะที่อ่านบทความนี้ ฉันขอแนะนำให้คุณอ่านสองบทความต่อไปนี้:
* "JavaScript Object Model-Execution Model" เขียนโดย Richie
* "ECMA-262 Third Edition" ส่วนใหญ่จะดูที่บทที่ 10 ซึ่งเป็นบริบทการดำเนินการ คำศัพท์ที่กล่าวถึงในบทความนี้มีรายละเอียดอธิบายอยู่ในนั้น
ในตอนท้าย Nicholas กล่าวถึง Mountain View JavaScript Meetup จริงๆ แล้วเว็บไซต์ Meetup เป็นเว็บไซต์ขององค์กรสำหรับกิจกรรมต่างๆ ในโลกแห่งความเป็นจริง คุณต้องข้ามไฟร์วอลล์เพื่อเข้าถึงมัน มีสิ่งดีๆ มากมาย กิจกรรมที่จะเข้าร่วมครับ อิอิ