ผลลัพธ์ของโปรแกรมต่อไปนี้คืออะไร?
คัดลอกรหัสรหัสดังต่อไปนี้:
วาร์ฟู = 1;
แถบฟังก์ชัน() {
ถ้า (!foo) {
วาร์ฟู = 10;
-
การแจ้งเตือน (foo);
-
บาร์();
ผลลัพธ์คือ 10;
แล้วอันนี้ล่ะ?
คัดลอกรหัสรหัสดังต่อไปนี้:
วาร์ ก = 1;
ฟังก์ชัน ข() {
ก = 10;
กลับ;
ฟังก์ชัน ก() {}
-
ข();
การแจ้งเตือน (ก);
ผลลัพธ์คือ 1
มันทำให้คุณกลัวไหม? เกิดอะไรขึ้น นี่อาจจะแปลก อันตราย และน่าสับสน แต่จริงๆ แล้วมันเป็นฟีเจอร์ภาษา JavaScript ที่มีประโยชน์และน่าประทับใจมากด้วย ฉันไม่รู้ว่ามีชื่อมาตรฐานสำหรับพฤติกรรมนี้หรือไม่ แต่ฉันชอบคำนี้: "Hoisting" บทความนี้จะให้คำอธิบายเบื้องต้นเกี่ยวกับกลไกนี้ แต่ก่อนอื่นให้เรามีความเข้าใจที่จำเป็นเกี่ยวกับขอบเขตของ JavaScript ก่อน
ขอบเขตของจาวาสคริปต์
สำหรับผู้เริ่มต้นใช้งาน Javascript หนึ่งในสิ่งที่น่าสับสนที่สุดคือขอบเขต ที่จริงแล้ว ไม่ใช่แค่ผู้เริ่มต้นเท่านั้น ฉันได้พบกับโปรแกรมเมอร์ JavaScript ที่มีประสบการณ์บางคน แต่พวกเขาไม่เข้าใจขอบเขตอย่างลึกซึ้ง สาเหตุที่ขอบเขต JavaScript สับสนก็เนื่องมาจากไวยากรณ์ของโปรแกรมนั้นดูเหมือนภาษาตระกูล C เหมือนกับโปรแกรม C ต่อไปนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
#รวม <stdio.h>
int หลัก() {
อินท์ x = 1;
printf("%d, ", x); // 1
ถ้า (1) {
อินท์ x = 2;
printf("%d, ", x); // 2
-
printf("%d/n", x); // 1
-
ผลลัพธ์ที่ได้คือ 1 2 1 เนื่องจากภาษาตระกูล C มีขอบเขตบล็อก เมื่อการควบคุมโปรแกรมเข้าสู่บล็อก เช่น บล็อก if จึงสามารถประกาศตัวแปรที่ส่งผลต่อบล็อกเท่านั้นโดยไม่กระทบต่อเอฟเฟกต์ภายนอกบล็อก โดเมน. แต่ใน Javascript สิ่งนี้ใช้ไม่ได้ ดูโค้ดด้านล่าง:
คัดลอกรหัสรหัสดังต่อไปนี้:
วาร์ x = 1;
console.log(x); // 1
ถ้า (จริง) {
วาร์ x = 2;
console.log(x); // 2
-
console.log(x); // 2
ผลลัพธ์จะเป็น 1 2 2 เพราะจาวาสคริปต์เป็นขอบเขตของฟังก์ชัน นี่คือความแตกต่างที่ใหญ่ที่สุดจากตระกูลภาษา C ถ้าในโปรแกรมนี้ไม่ได้สร้างขอบเขตใหม่
สำหรับโปรแกรมเมอร์ C, C++ และ Java จำนวนมาก นี่ไม่ใช่สิ่งที่พวกเขาคาดหวังและยินดี โชคดี เนื่องจากความยืดหยุ่นของฟังก์ชัน JavaScript จึงมีวิธีแก้ไขปัญหานี้ หากคุณต้องสร้างขอบเขตชั่วคราว ให้ดำเนินการดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
ฟังก์ชั่นฟู() {
วาร์ x = 1;
ถ้า (x) {
(การทำงาน () {
วาร์ x = 2;
// รหัสอื่น ๆ
-
-
// x ยังคงเป็น 1
-
วิธีการนี้มีความยืดหยุ่นและสามารถใช้ได้ทุกที่ที่คุณต้องการสร้างขอบเขตชั่วคราว ไม่ใช่แค่ภายในบล็อกเท่านั้น อย่างไรก็ตาม ฉันขอแนะนำอย่างยิ่งให้คุณใช้เวลาทำความเข้าใจการกำหนดขอบเขตของ JavaScript มันมีประโยชน์มากและเป็นหนึ่งในฟีเจอร์ JavaScript ที่ฉันชื่นชอบ หากคุณเข้าใจขอบเขต การยกแบบแปรผันจะเหมาะสมสำหรับคุณมากกว่า
การประกาศตัวแปร การตั้งชื่อ และการเลื่อนตำแหน่ง
ใน JavaScript มี 4 วิธีพื้นฐานสำหรับตัวแปรในการเข้าสู่ขอบเขต:
•1 ภาษาในตัว: ขอบเขตทั้งหมดมีสิ่งนี้และข้อโต้แย้ง (หมายเหตุผู้แปล: หลังจากการทดสอบ ข้อโต้แย้งจะไม่ปรากฏในขอบเขตส่วนกลาง)
•2 พารามิเตอร์ที่เป็นทางการ: พารามิเตอร์ที่เป็นทางการของฟังก์ชันจะเป็นส่วนหนึ่งของขอบเขตของเนื้อหาของฟังก์ชัน
•3 การประกาศฟังก์ชัน: รูปแบบนี้: function foo(){};
•4 การประกาศตัวแปร: เช่นนี้ var foo;
การประกาศฟังก์ชันและการประกาศตัวแปรจะถูก "ยก" ขึ้นอย่างเงียบ ๆ เสมอไปที่ด้านบนของเนื้อหาของวิธีการโดยล่าม ซึ่งหมายความว่ารหัสดังต่อไปนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
ฟังก์ชั่นฟู() {
บาร์();
วาร์ x = 1;
-
จริงๆ จะถูกตีความว่า:
คัดลอกรหัสรหัสดังต่อไปนี้:
ฟังก์ชั่นฟู() {
วาร์ x;
บาร์();
x = 1;
-
ไม่ว่าบล็อกที่กำหนดตัวแปรจะสามารถดำเนินการได้หรือไม่ จริงๆ แล้วสองฟังก์ชันต่อไปนี้เป็นสิ่งเดียวกัน:
คัดลอกรหัสรหัสดังต่อไปนี้:
ฟังก์ชั่นฟู() {
ถ้า (เท็จ) {
วาร์ x = 1;
-
กลับ;
วาร์ y = 1;
-
ฟังก์ชั่นฟู() {
วาร์ x, y;
ถ้า (เท็จ) {
x = 1;
-
กลับ;
ย = 1;
-
โปรดทราบว่าการกำหนดตัวแปรไม่ได้ถูกยกขึ้น เป็นเพียงการประกาศเท่านั้น อย่างไรก็ตาม การประกาศฟังก์ชันจะแตกต่างออกไปเล็กน้อย และส่วนเนื้อหาของฟังก์ชันก็ได้รับการเลื่อนระดับด้วย แต่โปรดทราบว่ามีสองวิธีในการประกาศฟังก์ชัน:
คัดลอกรหัสรหัสดังต่อไปนี้:
ทดสอบฟังก์ชัน() {
foo(); // TypeError "foo ไม่ใช่ฟังก์ชัน"
bar(); // "สิ่งนี้จะทำงาน!"
var foo = function () { // ตัวแปรชี้ไปที่การแสดงออกของฟังก์ชัน
alert("สิ่งนี้ไม่ทำงาน!");
-
function bar() { // ฟังก์ชันการประกาศฟังก์ชันที่ชื่อ bar
alert("สิ่งนี้จะทำงาน!");
-
-
ทดสอบ();
ในตัวอย่างนี้ เฉพาะการประกาศฟังก์ชันเท่านั้นที่จะถูกยกขึ้นพร้อมกับเนื้อหาของฟังก์ชัน การประกาศ foo จะถูกยกขึ้น แต่เนื้อหาของฟังก์ชันที่ชี้ไปจะถูกกำหนดระหว่างการดำเนินการเท่านั้น
ข้อมูลข้างต้นครอบคลุมถึงพื้นฐานบางประการของการเพิ่มพลัง และดูเหมือนจะไม่ทำให้เกิดความสับสนขนาดนั้น อย่างไรก็ตาม ในสถานการณ์พิเศษบางอย่าง ยังคงมีความซับซ้อนในระดับหนึ่ง
ลำดับการแยกวิเคราะห์ตัวแปร
สิ่งสำคัญที่สุดที่ต้องจำไว้คือลำดับความละเอียดของตัวแปร จำ 4 วิธีที่การตั้งชื่อเข้าสู่ขอบเขตที่ฉันให้ไว้ก่อนหน้านี้ได้ไหม ลำดับการแยกวิเคราะห์ตัวแปรคือลำดับที่ฉันแสดงรายการไว้
คัดลอกรหัสรหัสดังต่อไปนี้:
<สคริปต์>
ฟังก์ชัน ก(){
-
วาร์ ก;
alert(a);//พิมพ์เนื้อหาฟังก์ชันของ a
</สคริปต์>
<สคริปต์>
วาร์ ก;
ฟังก์ชัน ก(){
-
alert(a);//พิมพ์เนื้อหาฟังก์ชันของ a
</สคริปต์>
//แต่ให้ใส่ใจกับความแตกต่างระหว่างวิธีการเขียนสองวิธีต่อไปนี้:
<สคริปต์>
วาร์ ก=1;
ฟังก์ชัน ก(){
-
alert(a);//พิมพ์ออกมา 1
</สคริปต์>
<สคริปต์>
ฟังก์ชัน ก(){
-
วาร์ ก=1;
alert(a);//พิมพ์ออกมา 1
</สคริปต์>
มีข้อยกเว้น 3 ข้อที่นี่:
อาร์กิวเมนต์ชื่อในตัวมีพฤติกรรมแปลก ๆ ดูเหมือนว่าควรจะประกาศหลังจากพารามิเตอร์ที่เป็นทางการของฟังก์ชัน แต่ก่อนการประกาศฟังก์ชัน ซึ่งหมายความว่าหากมีข้อโต้แย้งในพารามิเตอร์ที่เป็นทางการ ก็จะมีลำดับความสำคัญมากกว่าพารามิเตอร์ในตัว นี่เป็นคุณลักษณะที่แย่มาก ดังนั้นให้หลีกเลี่ยงการใช้อาร์กิวเมนต์ในพารามิเตอร์ที่เป็นทางการ
2 การกำหนดตัวแปรนี้ไว้ที่ใดก็ได้จะทำให้เกิดข้อผิดพลาดทางไวยากรณ์ ซึ่งเป็นคุณลักษณะที่ดี
3. หากพารามิเตอร์ที่เป็นทางการหลายรายการมีชื่อเดียวกัน พารามิเตอร์สุดท้ายจะมีลำดับความสำคัญ แม้ว่าค่าของพารามิเตอร์จะไม่ได้ถูกกำหนดไว้ระหว่างการดำเนินการจริงก็ตาม
ฟังก์ชันที่มีชื่อ
คุณสามารถตั้งชื่อฟังก์ชันได้ หากเป็นเช่นนั้น จะไม่ใช่การประกาศฟังก์ชัน และชื่อฟังก์ชันที่ระบุ (หากมี เช่น สแปมด้านล่าง หมายเหตุของผู้แปล) ในคำจำกัดความเนื้อหาของฟังก์ชันจะไม่ได้รับการเลื่อนระดับ แต่จะถูกละเว้น นี่คือโค้ดบางส่วนที่จะช่วยให้คุณเข้าใจ:
คัดลอกรหัสรหัสดังต่อไปนี้:
foo(); // TypeError "foo ไม่ใช่ฟังก์ชัน"
บาร์(); // ถูกต้อง
baz(); // TypeError "baz ไม่ใช่ฟังก์ชัน"
สแปม(); // ReferenceError "ไม่ได้กำหนดสแปม"
var foo = function () {}; // foo ชี้ไปที่ฟังก์ชันที่ไม่ระบุชื่อ
แถบฟังก์ชัน() {}; // การประกาศฟังก์ชัน
var baz = function spam() {}; // ฟังก์ชันที่มีชื่อ มีเพียง baz เท่านั้นที่ได้รับการเลื่อนระดับ สแปมจะไม่ได้รับการเลื่อนระดับ
foo(); // ถูกต้อง
บาร์(); // ถูกต้อง
baz(); // ถูกต้อง
สแปม(); // ReferenceError "ไม่ได้กำหนดสแปม"
วิธีเขียนโค้ด
เมื่อคุณเข้าใจการกำหนดขอบเขตและการยกตัวแปรแล้ว การเข้ารหัส JavaScript หมายความว่าอย่างไร สิ่งที่สำคัญที่สุดคือต้องกำหนดตัวแปรของคุณด้วย var เสมอ และฉันขอแนะนำอย่างยิ่งว่าสำหรับชื่อหนึ่งๆ ควรมีการประกาศ var เพียงรายการเดียวในขอบเขตเสมอ หากคุณทำเช่นนี้ คุณจะไม่มีปัญหาเรื่องการยกขอบเขตและการยกตัวแปร
คุณหมายถึงอะไรโดยข้อกำหนดภาษา?
ฉันพบว่าเอกสารอ้างอิง ECMAScript มีประโยชน์เสมอ นี่คือสิ่งที่ฉันพบเกี่ยวกับการยกขอบเขตและการยกตัวแปร:
หากมีการประกาศตัวแปรในคลาสเนื้อหาของฟังก์ชัน ตัวแปรนั้นจะถือเป็นขอบเขตของฟังก์ชัน มิฉะนั้น จะถูกกำหนดขอบเขตทั่วโลก (เป็นคุณสมบัติของ global) ตัวแปรจะถูกสร้างขึ้นเมื่อการดำเนินการเข้าสู่ขอบเขต บล็อกจะไม่กำหนดขอบเขตใหม่ เฉพาะการประกาศฟังก์ชันและขั้นตอนเท่านั้น (นักแปลคิดว่าเป็นการเรียกใช้โค้ดระดับโลก) เท่านั้นที่จะสร้างขอบเขตใหม่ ตัวแปรจะเริ่มต้นเป็นไม่ได้กำหนดเมื่อถูกสร้างขึ้น หากมีการดำเนินการกำหนดในคำสั่งการประกาศตัวแปร การดำเนินการกำหนดจะเกิดขึ้นเมื่อมีการดำเนินการเท่านั้น ไม่ใช่เมื่อถูกสร้างขึ้น
ฉันหวังว่าบทความนี้จะนำแสงสว่างมาสู่โปรแกรมเมอร์ที่สับสนเกี่ยวกับ JavaScript ฉันยังพยายามอย่างเต็มที่เพื่อไม่ให้เกิดความสับสนมากขึ้น หากฉันพูดอะไรผิดหรือมองข้ามบางสิ่งบางอย่างโปรดแจ้งให้เราทราบ
ส่วนเสริมของผู้แปล
เพื่อนเตือนฉันเกี่ยวกับปัญหาการส่งเสริมฟังก์ชันที่มีชื่อในขอบเขตทั่วโลกภายใต้ IE:
นี่คือวิธีที่ฉันทดสอบเมื่อแปลบทความ:
คัดลอกรหัสรหัสดังต่อไปนี้:
<สคริปต์>
ฟังก์ชั่น(){
สแปม();
var baz = function spam() {alert('นี่คือสแปม')};
-
เสื้อ();
</สคริปต์>
วิธีการเขียนนี้ นั่นคือ การส่งเสริมฟังก์ชันที่มีชื่อในขอบเขตที่ไม่ใช่สากล มีประสิทธิภาพเหมือนกันภายใต้ ie และ ff ฉันเปลี่ยนเป็น:
คัดลอกรหัสรหัสดังต่อไปนี้:
<สคริปต์>
สแปม();
var baz = function spam() {alert('นี่คือสแปม')};
</สคริปต์>
จากนั้นสแปมสามารถดำเนินการได้ภายใต้เช่น แต่ไม่ใช่ภายใต้ ff นี่แสดงให้เห็นว่าเบราว์เซอร์ที่แตกต่างกันจัดการรายละเอียดนี้แตกต่างกัน
คำถามนี้ยังทำให้ฉันนึกถึงคำถามอีกสองข้อ 1: สำหรับตัวแปรที่มีขอบเขตทั่วโลก จะมีความแตกต่างระหว่าง var และ non-var หากไม่มี var ตัวแปรจะไม่ได้รับการเลื่อนระดับ ตัวอย่างเช่น ในสองโปรแกรมต่อไปนี้ โปรแกรมที่สองจะรายงานข้อผิดพลาด:
คัดลอกรหัสรหัสดังต่อไปนี้:
<สคริปต์>
การแจ้งเตือน (ก);
วาร์ ก=1;
</สคริปต์>
คัดลอกรหัสรหัสดังต่อไปนี้:
<สคริปต์>
การแจ้งเตือน (ก);
ก=1;
</สคริปต์>
2: ตัวแปรท้องถิ่นที่สร้างใน eval จะไม่ได้รับการเลื่อนระดับ (ไม่มีวิธีทำได้)
คัดลอกรหัสรหัสดังต่อไปนี้:
<สคริปต์>
วาร์ ก = 1;
ฟังก์ชัน t(){
การแจ้งเตือน (ก);
eval('var a = 2');
การแจ้งเตือน (ก);
-
เสื้อ();
การแจ้งเตือน (ก);
</สคริปต์>