เริ่มต้นด้วยคำถามง่ายๆ:
<script type="text/javascript">
แจ้งเตือน(i); // ?
วาร์ i = 1;
</สคริปต์>
ผลลัพธ์ที่ได้ไม่ได้กำหนดไว้ ปรากฏการณ์นี้เรียกว่า "การแยกวิเคราะห์ล่วงหน้า": เอ็นจิ้น JavaScript จะแยกวิเคราะห์ตัวแปร var และคำจำกัดความของฟังก์ชันก่อน รหัสจะไม่ถูกดำเนินการจนกว่าการแยกวิเคราะห์ล่วงหน้าจะเสร็จสมบูรณ์ หากสตรีมเอกสารประกอบด้วยส่วนโค้ดสคริปต์หลายส่วน (โค้ด js คั่นด้วยแท็กสคริปต์หรือไฟล์ js ที่นำเข้า) ลำดับการทำงานคือ:
ขั้นตอนที่ 1 อ่านส่วนโค้ดแรก
ขั้นตอนที่ 2 ทำการวิเคราะห์ไวยากรณ์ หากมีข้อผิดพลาด จะมีการรายงานข้อผิดพลาดทางไวยากรณ์ (เช่น วงเล็บไม่ตรงกัน เป็นต้น) และข้ามไปยังขั้นตอนที่ 5
ขั้นตอนที่ 3 ทำ "การแยกวิเคราะห์ล่วงหน้า" ของตัวแปร var และคำจำกัดความของฟังก์ชัน (จะไม่มีการรายงานข้อผิดพลาดเนื่องจากแยกวิเคราะห์การประกาศที่ถูกต้องเท่านั้น)
ขั้นตอนที่ 4 ดำเนินการส่วนของโค้ดและรายงานข้อผิดพลาดหากมีข้อผิดพลาด (เช่น ตัวแปรไม่ได้กำหนดไว้)
ขั้นตอนที่ 5 หากมีส่วนของโค้ดอื่น ให้อ่านส่วนของโค้ดถัดไปแล้วทำซ้ำขั้นตอนที่ 2
ขั้นตอนที่ 6 ในตอนท้ายของการวิเคราะห์ข้างต้นสามารถอธิบายปัญหาได้มากมาย แต่ฉันมักจะรู้สึกว่ามีบางอย่างขาดหายไป ตัวอย่างเช่น ในขั้นตอนที่ 3 "การแยกวิเคราะห์ล่วงหน้า" คืออะไรกันแน่ และในขั้นตอนที่ 4 ให้ดูตัวอย่างต่อไปนี้:
<script type="text/javascript">
การแจ้งเตือน (i); // ข้อผิดพลาด: ฉันไม่ได้กำหนดไว้
ฉัน = 1;
</สคริปต์>
เหตุใดประโยคแรกจึงทำให้เกิดข้อผิดพลาด ใน JavaScript ไม่จำเป็นต้องกำหนดตัวแปรใช่ไหม
เวลาของกระบวนการเรียบเรียงผ่านไปราวกับม้าขาว และฉันก็เปิด "หลักการของการเรียบเรียง" ข้างตู้หนังสือราวกับอยู่อีกโลกหนึ่ง มีข้อความนี้อยู่ในช่องว่างที่คุ้นเคยแต่ไม่คุ้นเคย:
สำหรับภาษาที่เรียบเรียงแบบดั้งเดิม ขั้นตอนการคอมไพล์แบ่งออกเป็น: การวิเคราะห์คำศัพท์และการวิเคราะห์ไวยากรณ์ การตรวจสอบความหมาย การเพิ่มประสิทธิภาพโค้ด และการสร้างไบต์
แต่สำหรับภาษาที่แปล หลังจากที่แผนผังไวยากรณ์ได้รับผ่านการวิเคราะห์คำศัพท์และการวิเคราะห์ไวยากรณ์ การตีความและการดำเนินการก็สามารถเริ่มต้นได้
พูดง่ายๆ ก็คือ การวิเคราะห์คำศัพท์คือการแปลงสตรีมอักขระ (สตรีมถ่าน) เป็นสตรีมโทเค็น (สตรีมโทเค็น) เช่นการแปลง c = a - b เป็น:
NAME "c"
เท่ากับ
ชื่อ "ก"
ลบ
ชื่อ "ข"
อัฒภาค
ข้างต้นเป็นเพียงตัวอย่าง สำหรับข้อมูลเพิ่มเติม โปรดดู Lexical Analysis
บทที่ 2 ของ "The Definitive Guide to JavaScript" พูดถึงโครงสร้างคำศัพท์ ซึ่งมีอธิบายไว้ใน ECMA-262 เช่นกัน โครงสร้างคำศัพท์เป็นพื้นฐานของภาษาและง่ายต่อการเชี่ยวชาญ สำหรับการนำการวิเคราะห์คำศัพท์ไปใช้ นั่นเป็นการวิจัยอีกแขนงหนึ่งและจะไม่กล่าวถึงในที่นี้
เราสามารถใช้การเปรียบเทียบภาษาธรรมชาติได้ การวิเคราะห์คำศัพท์นั้นเป็นการแปลแบบยากๆ แบบตัวต่อตัว เช่น ถ้าย่อหน้าของภาษาอังกฤษถูกแปลเป็นคำภาษาจีนทีละคำ เราก็ได้โทเค็นจำนวนมากซึ่งเป็นเรื่องยาก ที่จะเข้าใจ การแปลเพิ่มเติมจำเป็นต้องมีการวิเคราะห์ทางไวยากรณ์ รูปต่อไปนี้คือแผนผังไวยากรณ์ของคำสั่งแบบมีเงื่อนไข:
เมื่อสร้าง syntax tree หากพบว่าไม่สามารถสร้างได้ เช่น if(a { i = 2; } จะมีการรายงาน syntax error และการแยกวิเคราะห์บล็อคโค้ดทั้งหมดจะสิ้นสุด นี่คือขั้นตอนที่ 2 ที่ จุดเริ่มต้นของบทความนี้
ด้วยการวิเคราะห์ไวยากรณ์ ให้สร้างหลังแผนผังไวยากรณ์ ประโยคที่แปลแล้วอาจจะยังคลุมเครือ และจำเป็นต้องมีการตรวจสอบความหมายเพิ่มเติม สำหรับภาษาที่พิมพ์อย่างรุนแรงแบบดั้งเดิม ส่วนหลักของการตรวจสอบความหมายคือการตรวจสอบประเภท เช่น พารามิเตอร์ที่แท้จริงของฟังก์ชันและประเภทพารามิเตอร์ที่เป็นทางการตรงกันหรือไม่ สำหรับภาษาที่พิมพ์ไม่ชัดเจน ขั้นตอนนี้อาจใช้ไม่ได้ (ฉันมีพลังงานจำกัดและไม่มีเวลาดูการใช้งานกลไก JS ดังนั้นฉันจึงไม่แน่ใจว่ามี ขั้นตอนการตรวจสอบความหมายในเอ็นจิ้น JS)
ปรากฎว่าสำหรับเอ็นจิ้น JavaScript จะต้องมีการวิเคราะห์คำศัพท์และการวิเคราะห์ไวยากรณ์ จากนั้นอาจมีขั้นตอนต่างๆ เช่น การตรวจสอบความหมายและการเพิ่มประสิทธิภาพโค้ด หลังจากขั้นตอนการคอมไพล์เหล่านี้เสร็จสิ้น (ภาษาใดก็ได้ กระบวนการคอมไพล์ แต่ภาษาที่ตีความไม่ได้ถูกคอมไพล์เป็นรหัสไบนารี่)
กระบวนการคอมไพล์ข้างต้นยังคงไม่สามารถอธิบาย "การแยกวิเคราะห์ล่วงหน้า" ในตอนต้นของบทความได้ เราต้องสำรวจการดำเนินการอย่างรอบคอบ กระบวนการของโค้ด JavaScript
Zhou Aimin กล่าวใน "The Essence of the JavaScript Language" ส่วนที่สองของ "Programming Practice" มีการวิเคราะห์อย่างรอบคอบเกี่ยวกับเรื่องนี้ นี่คือข้อมูลเชิงลึกบางส่วนของฉัน:
จากการคอมไพล์โค้ด JavaScript แปลเป็นแผนผังไวยากรณ์แล้วจะดำเนินการทันทีตามแผนผังไวยากรณ์
ซึ่งต้องมีการดำเนินการเพิ่มเติม ทำความเข้าใจเกี่ยวกับกลไกขอบเขตของ JavaScript ในแง่ของคนธรรมดา ขอบเขตของตัวแปร JavaScript จะถูกกำหนดเมื่อมีการกำหนด แทนที่จะดำเนินการ กล่าวคือ ขอบเขตคำศัพท์ขึ้นอยู่กับซอร์สโค้ด คอมไพเลอร์สามารถตรวจสอบได้ผ่านการวิเคราะห์แบบคงที่ ดังนั้นขอบเขตคำศัพท์จึงเรียกว่าขอบเขตแบบคงที่ อย่างไรก็ตาม ควรสังเกตว่าความหมายของ with และการประเมินไม่สามารถรับรู้ได้ผ่านเทคโนโลยีคงที่เท่านั้น จริงๆ แล้ว เราสามารถพูดถึงกลไกขอบเขตของ JS เท่านั้น
เมื่อกลไก JS ดำเนินการแต่ละฟังก์ชัน บริบทการดำเนินการจะมี อ็อบเจ็กต์การเรียกคือโครงสร้าง scriptObject ที่ใช้ในการบันทึกตารางตัวแปรภายใน เช่น varDecls, funDecls ของตารางฟังก์ชันแบบฝัง และค่าอัปค่าของรายการอ้างอิงพาเรนต์ (หมายเหตุ: ข้อมูล เช่น varDecls และ funDecls จะได้รับในระหว่าง ขั้นตอนการวิเคราะห์ไวยากรณ์และบันทึกไว้ในแผนผังไวยากรณ์ เมื่อเรียกใช้ฟังก์ชันอินสแตนซ์ ข้อมูลนี้จะถูกคัดลอกจากแผนผังไวยากรณ์ไปยัง scriptObject) scriptObject เป็นระบบคงที่ที่เกี่ยวข้องกับฟังก์ชัน ซึ่งสอดคล้องกับวงจรชีวิตของอินสแตนซ์ฟังก์ชัน .
ขอบเขตคำศัพท์เป็นกลไกขอบเขตของ JS และคุณต้องเข้าใจวิธีการนำไปใช้ด้วย นี่คือห่วงโซ่ขอบเขต ห่วงโซ่ขอบเขตเป็นกลไกการค้นหาชื่อ อันดับแรกจะค้นหา scriptObject ในสภาพแวดล้อมการดำเนินการปัจจุบัน หากไม่พบ จะติดตามค่าที่เพิ่มขึ้นของ scriptObject หลักและค้นหาออบเจ็กต์โกลบอล
เมื่อมีการดำเนินการอินสแตนซ์ของฟังก์ชัน การปิดจะถูกสร้างขึ้นหรือเชื่อมโยงกับอินสแตนซ์นั้น scriptObject ใช้เพื่อบันทึกตารางตัวแปรที่เกี่ยวข้องกับฟังก์ชันแบบคงที่ ในขณะที่การปิดจะบันทึกตารางตัวแปรเหล่านี้แบบไดนามิกและค่าที่ทำงานระหว่างการดำเนินการ วงจรชีวิตของการปิดอาจยาวนานกว่าวงจรชีวิตของอินสแตนซ์ฟังก์ชัน อินสแตนซ์ของฟังก์ชันจะถูกทำลายโดยอัตโนมัติหลังจากที่การอ้างอิงที่ใช้งานอยู่ว่างเปล่า และการปิดจะถูกรีไซเคิลโดยกลไก JS หลังจากที่การอ้างอิงข้อมูลว่างเปล่า (ในบางกรณี จะไม่ถูกรีไซเคิลโดยอัตโนมัติ ส่งผลให้หน่วยความจำรั่ว)
อย่ากลัวกับคำนามมากมายที่กล่าวมาข้างต้น เมื่อคุณเข้าใจแนวคิดของสภาพแวดล้อมการดำเนินการ การเรียกวัตถุ การปิด ขอบเขตคำศัพท์ และสายโซ่ขอบเขต ปรากฏการณ์ต่างๆ ในภาษา JS ก็สามารถแก้ไขได้อย่างง่ายดาย
สรุป ณ จุดนี้ คำถามที่จุดเริ่มต้นของบทความสามารถอธิบายได้อย่างชัดเจนมาก:
สิ่งที่เรียกว่า "การแยกวิเคราะห์ล่วงหน้า" ในขั้นตอนที่ 3 จริง ๆ แล้วเสร็จสมบูรณ์ในขั้นตอนการวิเคราะห์ไวยากรณ์ของขั้นตอนที่ 2 และจัดเก็บไว้ในแผนผังไวยากรณ์ เมื่ออินสแตนซ์ของฟังก์ชันถูกดำเนินการ varDelcs และ funcDecls จะถูกคัดลอกจากแผนผังไวยากรณ์ไปยัง scriptObject ของสภาพแวดล้อมการดำเนินการ
ในขั้นตอนที่ 4 ตัวแปรที่ไม่ได้กำหนดหมายความว่าไม่พบในตารางตัวแปรของ scriptObject กลไก JS จะค้นหาขึ้นไปตามค่าที่เพิ่มขึ้นของ scriptObject หากไม่พบทั้งสองรายการ การดำเนินการเขียน i = 1 จะเทียบเท่ากับหน้าต่างในที่สุด i = 1; เพิ่มคุณลักษณะใหม่ให้กับวัตถุหน้าต่าง สำหรับการดำเนินการอ่าน หากไม่พบ scriptObject ที่ติดตามกลับไปยังสภาพแวดล้อมการดำเนินการส่วนกลาง ข้อผิดพลาดรันไทม์จะเกิดขึ้น
หลังจากทำความเข้าใจ หมอกก็จางลง ดอกไม้ก็เบ่งบาน ท้องฟ้าก็แจ่มใส
สุดท้ายนี้ ฉันทิ้งคำถามไว้ให้คุณ:
<script type="text/javascript">
var หาเรื่อง = 1;
ฟังก์ชั่น foo(หาเรื่อง) {
การแจ้งเตือน (หาเรื่อง);
วาร์หาเรื่อง = 2;
-
ฟู(3);
</สคริปต์>
ผลลัพธ์ของการแจ้งเตือนคืออะไร?