JavaScript Under The Hood: วนรอบเหตุการณ์
เรามาฝึกฝน JavaScript ให้เชี่ยวชาญด้วยการสำรวจการทำงานตั้งแต่เริ่มต้น
คุณเคยประสบกับข้อผิดพลาดที่ไม่ได้กำหนดหรือมีปัญหาในการระบุขอบเขตของตัวแปรหรือไม่?
ใช้เวลานานมากในการดีบักข้อผิดพลาดใดๆ โดยไม่รู้ว่ารหัสทำงานอย่างไร
ในบล็อกนี้ ฉันจะแสดงให้เห็นว่าแนวคิดขั้นสูง เช่นevent loopที่เกี่ยวกับexecution context, call stackและcallback queueใช้งานได้จริงอย่างไร
ข้อจำกัดความรับผิดชอบ — แนวคิดนี้ต้องใช้ความรู้อย่างมากและเชื่อมโยงถึงกัน ดังนั้นโปรดอย่าแม้แต่จะกระพริบตา!
เครื่องมือจาวาสคริปต์
เครื่องยนต์ V8 ของ Google เป็นภาพประกอบที่รู้จักกันดีของเครื่องยนต์ JavaScript ตัวอย่างเช่น Chrome และ Node.js ต่างก็ใช้เครื่องยนต์ V8 โดยพื้นฐานแล้ว เครื่องยนต์ V8 ประกอบด้วยสองส่วน—
- Call Stack :รหัสทั้งหมดจะถูกดำเนินการภายในนั้น มันทำงานเหมือนกับโครงสร้างข้อมูลแบบ Stack ซึ่งหมายถึงการทำตามแนวคิด LIFO (เข้าก่อนออกก่อน)
- ฮีปหน่วยความจำ:ตำแหน่งที่เกิดการจัดสรรหน่วยความจำ ซึ่งแตกต่างจากโครงสร้างข้อมูลกองมันเป็นเพียงหน่วยความจำ
Global Execution Context หรือ GEC คือบริบทการดำเนินการเริ่มต้นที่สร้างโดยเครื่องมือ JavaScript เมื่อใดก็ตามที่ได้รับไฟล์สคริปต์
โค้ด JavaScript ทั้งหมดที่ไม่ได้อยู่ในฟังก์ชันจะทำงานในGEC. ขั้นตอนต่อไปนี้ดำเนินการในGEC—
- สร้างพื้นที่หน่วยความจำเพื่อจัดเก็บตัวแปรและฟังก์ชันทั้งหมดในระดับโลก
- สร้างวัตถุส่วนกลาง
- สร้างคำหลัก
this
ขึ้นอยู่กับตำแหน่งที่รหัสของคุณจะถูกดำเนินการจะเป็นตัวกำหนดthisตำแหน่งที่ตั้ง ตัวอย่างเช่น ใน Node.js จะชี้ไปที่ออบเจ็กต์ส่วนกลางที่แตกต่างกัน ในขณะที่ในเบราว์เซอร์จะชี้ไปที่windowออบเจ็กต์
Call Stack (หรือ Function Execution Stack)
JavaScript คือsingle-threadedตามที่คุณเคยได้ยินมาแล้ว แต่จริงๆแล้วมันหมายถึงอะไร?
หมายความว่าเอ็นจิ้น JavaScript มีเพียงหนึ่งcall stackหรือfunction execution stack.
- ดังที่เราทราบ เมื่อใดก็ตามที่คอมไพเลอร์สำรวจโค้ดของคุณเป็นครั้งแรก เอ็นจิ้น JS จะถูกขอให้สร้าง
Global Execution ContextหรือGECโดยคอมไพเลอร์ รวมทั้งขอให้วางลงในไฟล์Call Stack. - รหัสทั้งหมดของคุณถูกดำเนินการทีละรหัสใน
Global Execution Contextและจัดสรรหน่วยความจำสำหรับการกำหนดฟังก์ชันหรือการประกาศตัวแปร และเก็บไว้ที่นั่น - แต่เมื่อพบการเรียกใช้ฟังก์ชัน a
Functional Execution ContextหรือFECถูกสร้างขึ้นเพื่อเรียกใช้โค้ดของฟังก์ชัน จากนั้นจึงเพิ่มไปที่ด้านบนสุดของcall stack. - ล่ามจะลบฟังก์ชันออกจาก
call stackทุกครั้งที่ฟังก์ชันยุติ ฟังก์ชันยุติ — เมื่อถึงจุดสิ้นสุดของขอบเขตหรือคำสั่งส่งคืน - สุดท้าย การรันโค้ดทั้งหมดของคุณ
GECจะถูกลบออกจากไฟล์Call Stack.
ไม่ต้องกังวล เรามาสาธิตด้วยตัวอย่างกัน
function f1{
console.log('f1');
}
f1();
console.log('end');
ขั้นตอนที่ 2: —ในตัวอย่างของเรา บรรทัดที่ 1 จะถูกดำเนินการซึ่งก็f1คือ หน่วยความจำจะถูกจัดสรรf1และจัดเก็บคำจำกัดความของมัน
ขั้นตอนที่ 3: —ในบรรทัดที่ 2 มีการเรียกใช้ฟังก์ชัน สำหรับการเรียกใช้ฟังก์ชันนี้ a Function Execution Context หรือFECจะถูกสร้างขึ้นและจะถูกเก็บไว้ที่ด้านบนCall Stackของ
ขั้นตอนที่ 4: —ตอนนี้ ทั้งหมดf1()จะถูกดำเนินการทีละบรรทัด และหลังจากเสร็จสิ้นการดำเนินการ มันจะถูกลบออกจากไฟล์Call Stack.
ขั้นตอนที่ 5: —จากนั้น บรรทัดสุดท้ายconsole.log('end')จะถูกดำเนินการ และจะพิมพ์ 'end' บนคอนโซล สุดท้าย การรันโค้ดทั้งหมดของคุณGlobal Execution Contextจะถูกลบออกจากไฟล์Call Stack.
JS จัดการงานอะซิงโครนัสอย่างไร
อย่างที่เราทราบกันดีว่าจาวาสคริปต์เป็น ภาษาแบบ ซิงโครนัสแบบเธรดเดียว (ทีละงาน) และเธรดแบบเดี่ยวจะcall stackทำงานทันทีไม่ว่าจะเกิดอะไรขึ้นก็ตาม
แต่ถ้าเราต้องดำเนินการบางอย่างหลังจาก 5 วินาทีล่ะ เราสามารถทำสิ่งนั้นในcall stack?
ไม่เราทำไม่ได้ เพราะcall stackไม่มีตัวจับเวลา แต่เราจะทำอย่างนั้นได้อย่างไร?
นี่คือที่มา ของ รันไทม์ JavaScript
อาจเสียใจถ้าฉันบอกคุณตอนนี้ - setTimeout()ไม่ได้เป็นส่วนหนึ่งของ JavaScript แม้ว่าconsole.log()หรือเหตุการณ์ DOMทั้งหมดจะเป็นส่วนหนึ่งของWeb APIที่ให้การเข้าถึงJavaScript Engineเพื่อใช้คุณสมบัติทั้งหมดในGlobal Execution Context (GEC)ผ่าน วัตถุwindowส่วนกลาง Web APIเหล่านี้เรียกว่าอะซิงโครนัส
Callback Queue คืออะไร?
คิวของงานที่เรียกว่า "คิวการโทรกลับ" หรือ "คิวงาน" คือคิวที่ดำเนินการหลังจากหน้าที่ปัจจุบันของคอลแต็กเสร็จสมบูรณ์แล้ว งานที่ลงทะเบียนในเว็บ API จะย้ายจากเว็บ API ไปยังคิวการเรียกกลับ
คิวการเรียกกลับทำงานเหมือนกับโครงสร้างข้อมูลคิว หมายความว่างานจะถูกจัดการตามลำดับ FIFO (เข้าก่อน ออกก่อน) ซึ่งตรงข้ามกับ call stack ซึ่งหมายความว่างานจะถูกจัดการตามลำดับที่เพิ่มเข้าไปในคิว
Event Loop คืออะไร?
ลูปเหตุการณ์ JavaScript เพิ่มงานจากคิวการโทรกลับไปยัง call stack ตามลำดับ FIFO ทันทีที่ call stack ว่างเปล่า
ลูปเหตุการณ์จะถูกบล็อกหาก call stack กำลังรันโค้ดบางอย่างอยู่ และจะไม่เพิ่มการโทรเพิ่มเติมจากคิวจนกว่า stack จะว่างเปล่าอีกครั้ง นี่เป็นเพราะโค้ด JavaScript ทำงานแบบรันจนจบ
ลองทำความเข้าใจกับแนวคิดข้างต้นด้วยตัวอย่าง
- ในตอนแรก
Global Execution Contextถูกสร้างขึ้นสำหรับรหัสของเราภายในของเราcall stackและGECดำเนินการบรรทัดรหัสของเราทีละบรรทัด GECรันบรรทัดที่ 1 และพิมพ์ 'Start' บนคอนโซลของเรา- เมื่อดำเนินการบรรทัดที่ 2
setTimeout()เว็บ API จะถูกเรียก จากนั้นจึงsetTimeout()ให้สิทธิ์เข้าถึงคุณสมบัติตัวจับเวลา จากนั้นคุณจะสามารถตั้ง5000msเวลาหน่วงเวลาได้ - เมื่อคุณส่ง
callBack()ฟังก์ชันผ่าน ฟังก์ชันsetTimeout()นั้นcallBack()จะถูกลงทะเบียนเป็น Call Back over web Web API - จากนั้น
GECดำเนินการบรรทัดที่ 1 รวมทั้งพิมพ์ 'End' บนคอนโซล - เมื่อโค้ดทั้งหมดถูกดำเนินการ
GECจะถูกลบออกจากcall stackไฟล์ . - หลังจากฟังก์ชันที่ลงทะเบียนใน
5000 millisecondจะถูกย้ายภายในcallBack()web APIcall Back Queue Event loopใส่callBack()ฟังก์ชันลงในcall Stackเมื่อเสร็จแล้วทุกอย่างก็ทำงาน และสุดท้ายcallBack()ฟังก์ชันจะทำงานและพิมพ์ 'โทรกลับ' ลงในคอนโซล
function f1() {
console.log('f1');
}
function f2() {
console.log('f2');
}
function main() {
console.log('main');
setTimeout(f1, 0);
f2();
}
main();
หากคุณคิดว่า "f1" จะพิมพ์ก่อน "f2" แสดงว่าคุณคิดผิด มันจะเป็น -
main
f2
f1
กลไกที่กล่าวถึงของ JavaScript เหมาะสำหรับฟังก์ชันการเรียกกลับหรือคำขอ API
ลองสังเกตทีละขั้นตอนว่าตัวอย่างที่ 2 ทำงานอย่างไรในสภาพแวดล้อมรันไทม์
- ในตอนแรก
GECจะถูกสร้างขึ้นภายใน และโค้ดจะถูกดำเนินการ ทีcall stackละบรรทัดในGECมันเก็บคำจำกัดความของฟังก์ชั่นทั้งหมดในMemory Heap - เมื่อ
main()มีการเรียก aFunction Execution Context (FEC)จะถูกสร้างขึ้นและจากนั้นจะเข้าสู่ call stack หลังจากนั้นโค้ดทั้งหมดของmain()ฟังก์ชันจะถูกดำเนินการทีละบรรทัด - มีบันทึกคอนโซลเพื่อพิมพ์คำหลัก ดังนั้นการ
console.log('main')ดำเนินการและออกไปจากสแต็ก - API ของ
setTimeout()เบราว์เซอร์เกิดขึ้น ฟังก์ชันการโทรกลับทำให้อยู่ในคิวการโทรกลับ แต่ในสแต็ก การดำเนินการจะเกิดขึ้นตามปกติ ดังนั้นf2()เข้าไปในสแต็ก บันทึกคอนโซลของการf2()ดำเนิน การ ทั้งสองออกไปจากกอง - แล้วมัน
main()ก็โผล่ออกมาจากสแต็ค - วนรอบเหตุการณ์จะรับรู้ว่า call stack ว่างเปล่า และมีฟังก์ชัน callback อยู่ในคิว ดังนั้นฟังก์ชันการโทรกลับ
f1()จะถูกใส่ลงในสแต็กโดยไฟล์event loop. การดำเนินการเริ่มต้นขึ้น บันทึกของคอนโซลดำเนินการ และf1()เด้งออกจากสแต็กด้วย และสุดท้าย ไม่มีอะไรอยู่ในสแต็กและคิวที่จะดำเนินการต่อไป
มันเป็นการปฏิบัติและบันทึกของฉัน หากคุณพบว่ามีประโยชน์ โปรดแสดงการสนับสนุนของคุณโดยคลิกที่ไอคอนตบมือด้านล่าง ติดตามผมได้ทางสื่อ





































![รายการที่เชื่อมโยงคืออะไร? [ส่วนที่ 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)