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 API
call 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()
เด้งออกจากสแต็กด้วย และสุดท้าย ไม่มีอะไรอยู่ในสแต็กและคิวที่จะดำเนินการต่อไป
มันเป็นการปฏิบัติและบันทึกของฉัน หากคุณพบว่ามีประโยชน์ โปรดแสดงการสนับสนุนของคุณโดยคลิกที่ไอคอนตบมือด้านล่าง ติดตามผมได้ทางสื่อ