คำตอบของFabrícioคือจุดบน; แต่ฉันต้องการเติมเต็มคำตอบของเขาด้วยเทคนิคที่น้อยกว่าซึ่งเพ่งความสนใจไปที่การเปรียบเทียบเพื่อช่วยอธิบายแนวคิดของความไม่แน่นอน
การเปรียบเทียบ ...
เมื่อวานนี้งานที่ฉันทำต้องการข้อมูลจากเพื่อนร่วมงาน ฉันรังเขา นี่คือวิธีการสนทนา:
ฉัน : สวัสดีบ๊อบผมจำเป็นต้องรู้วิธีการที่เราFoo 'งบาร์ ' วันที่สัปดาห์ที่ผ่านมา จิมต้องการรายงานเกี่ยวกับมันและคุณเป็นคนเดียวที่รู้รายละเอียดเกี่ยวกับมัน
Bob : แน่นอน แต่ใช้เวลาประมาณ 30 นาที
ฉัน : นั่นคือบ๊อบที่ยอดเยี่ยม ให้แหวนกลับมาให้ฉันเมื่อคุณได้รับข้อมูล!
ณ จุดนี้ฉันวางสายโทรศัพท์ เนื่องจากฉันต้องการข้อมูลจาก Bob เพื่อทำรายงานให้สมบูรณ์ฉันจึงออกจากรายงานและไปซื้อกาแฟแทนจากนั้นฉันก็ไปตามอีเมล 40 นาทีต่อมา (บ๊อบช้า) บ๊อบโทรกลับและให้ข้อมูลที่ฉันต้องการ ณ จุดนี้ฉันกลับมาทำงานกับรายงานของฉันเนื่องจากฉันมีข้อมูลทั้งหมดที่ฉันต้องการ
ลองนึกภาพว่าบทสนทนาเป็นแบบนี้แทนไหม
ฉัน : สวัสดีบ๊อบผมจำเป็นต้องรู้วิธีการที่เราFoo 'งบาร์ ' วันที่สัปดาห์ที่ผ่านมา จิมต้องการรายงานเกี่ยวกับมันและคุณเป็นคนเดียวที่รู้รายละเอียดเกี่ยวกับมัน
Bob : แน่นอน แต่ใช้เวลาประมาณ 30 นาที
ฉัน : นั่นคือบ๊อบที่ยอดเยี่ยม ฉันจะรอ.
และฉันนั่งที่นั่นและรอ และรอ และรอ เป็นเวลา 40 นาที ไม่ทำอะไรเลยนอกจากรอ ในที่สุดบ๊อบก็ให้ข้อมูลกับฉันเราวางหูและฉันก็ทำรายงานให้เสร็จ แต่ฉันเสียเวลาในการผลิต 40 นาที
นี่เป็นพฤติกรรมแบบอะซิงโครนัสกับซิงโครนัส
นี่คือสิ่งที่เกิดขึ้นในตัวอย่างทั้งหมดในคำถามของเรา การโหลดอิมเมจการโหลดไฟล์จากดิสก์และการขอเพจผ่าน AJAX นั้นเป็นการดำเนินการที่ช้า (ในบริบทของการคำนวณสมัยใหม่)
แทนที่จะรอให้การดำเนินการช้าเหล่านี้เสร็จสมบูรณ์ JavaScript ช่วยให้คุณลงทะเบียนฟังก์ชั่นการโทรกลับซึ่งจะดำเนินการเมื่อการดำเนินการช้าเสร็จสมบูรณ์ อย่างไรก็ตามในระหว่างนี้ JavaScript จะยังคงเรียกใช้รหัสอื่น ๆ ความจริงที่ว่า JavaScript รันรหัสอื่น ๆในขณะที่รอการดำเนินการช้าจะเสร็จสมบูรณ์ทำให้ลักษณะการทำงานไม่ตรงกัน หากจาวาสคริปต์รอให้การดำเนินการเสร็จสิ้นก่อนที่จะดำเนินการกับรหัสอื่น ๆ นี่จะเป็นลักษณะการทำงานแบบซิงโครนัส
var outerScopeVar;
var img = document.createElement('img');
// Here we register the callback function.
img.onload = function() {
// Code within this function will be executed once the image has loaded.
outerScopeVar = this.width;
};
// But, while the image is loading, JavaScript continues executing, and
// processes the following lines of JavaScript.
img.src = 'lolcat.png';
alert(outerScopeVar);
ในโค้ดด้านบนเราจะขอให้โหลด JavaScript lolcat.png
ซึ่งเป็นการดำเนินการที่ยุ่งเหยิง ฟังก์ชันการเรียกกลับจะถูกดำเนินการเมื่อการดำเนินการช้านี้เสร็จ แต่ในระหว่างนี้ JavaScript จะประมวลผลบรรทัดถัดไปของรหัส alert(outerScopeVar)
กล่าวคือ
นี่คือเหตุผลที่เราเห็นการแจ้งเตือนแสดงundefined
; เนื่องจากalert()
มีการประมวลผลทันทีแทนที่จะโหลดภาพ
เพื่อแก้ไขรหัสของเราสิ่งที่เราต้องทำคือย้ายalert(outerScopeVar)
รหัสไปยังฟังก์ชันการเรียกกลับ ด้วยเหตุนี้เราจึงไม่ต้องการouterScopeVar
ตัวแปรที่ประกาศเป็นตัวแปรโกลบอลอีกต่อไป
var img = document.createElement('img');
img.onload = function() {
var localScopeVar = this.width;
alert(localScopeVar);
};
img.src = 'lolcat.png';
คุณจะมักจะเห็นการเรียกกลับมีการระบุเป็นฟังก์ชั่นเนื่องจากว่าเป็นเพียงวิธี * ใน JavaScript เพื่อกำหนดรหัสบางส่วน แต่ไม่รันมันจนกระทั่งต่อมา
ดังนั้นในตัวอย่างทั้งหมดของเราfunction() { /* Do something */ }
การเรียกกลับคือ ในการแก้ไขปัญหาทุกตัวอย่างทั้งหมดที่เราต้องทำคือการย้ายโค้ดที่ต้องการการตอบสนองของการดำเนินการเข้าไปในที่นั่น!
* ในทางเทคนิคคุณสามารถใช้eval()
เช่นกัน แต่eval()
เป็นความชั่วร้ายสำหรับวัตถุประสงค์นี้
ฉันจะรอผู้โทรได้อย่างไร
ขณะนี้คุณอาจมีรหัสคล้ายกัน
function getWidthOfImage(src) {
var outerScopeVar;
var img = document.createElement('img');
img.onload = function() {
outerScopeVar = this.width;
};
img.src = src;
return outerScopeVar;
}
var width = getWidthOfImage('lolcat.png');
alert(width);
อย่างไรก็ตามตอนนี้เรารู้ว่าreturn outerScopeVar
เกิดขึ้นทันที ก่อนที่onload
ฟังก์ชั่นการโทรกลับจะอัปเดตตัวแปร สิ่งนี้นำไปสู่การgetWidthOfImage()
กลับมาundefined
และundefined
การแจ้งเตือน
ในการแก้ไขปัญหานี้เราต้องอนุญาตให้ฟังก์ชันการโทรgetWidthOfImage()
ลงทะเบียนการโทรกลับจากนั้นย้ายการแจ้งเตือนของความกว้างเพื่อให้อยู่ภายในการติดต่อกลับนั้น
function getWidthOfImage(src, cb) {
var img = document.createElement('img');
img.onload = function() {
cb(this.width);
};
img.src = src;
}
getWidthOfImage('lolcat.png', function (width) {
alert(width);
});
... เหมือนก่อนโปรดทราบว่าเราสามารถลบตัวแปรโกลบอลได้ (ในกรณีนี้width
)