จะอธิบายการโทรกลับเป็นภาษาอังกฤษธรรมดาได้อย่างไร พวกเขาแตกต่างจากการเรียกฟังก์ชั่นหนึ่งจากฟังก์ชั่นอื่นที่รับบริบทบางอย่างจากฟังก์ชั่นการโทรได้อย่างไร? พลังของพวกเขาจะถูกอธิบายต่อโปรแกรมเมอร์มือใหม่ได้อย่างไร?
จะอธิบายการโทรกลับเป็นภาษาอังกฤษธรรมดาได้อย่างไร พวกเขาแตกต่างจากการเรียกฟังก์ชั่นหนึ่งจากฟังก์ชั่นอื่นที่รับบริบทบางอย่างจากฟังก์ชั่นการโทรได้อย่างไร? พลังของพวกเขาจะถูกอธิบายต่อโปรแกรมเมอร์มือใหม่ได้อย่างไร?
คำตอบ:
บ่อยครั้งที่แอปพลิเคชั่นต้องการเรียกใช้ฟังก์ชันที่แตกต่างกันตามบริบท / สถานะ สำหรับสิ่งนี้เราใช้ตัวแปรที่เราจะเก็บข้อมูลเกี่ยวกับฟังก์ชั่นที่จะเรียกว่า ตามความต้องการแอปพลิเคชันจะตั้งค่าตัวแปรนี้ด้วยข้อมูลเกี่ยวกับฟังก์ชันที่จะเรียกใช้และจะเรียกใช้ฟังก์ชันโดยใช้ตัวแปรเดียวกัน
ใน javascript ตัวอย่างอยู่ด้านล่าง ที่นี่เราใช้วิธีการโต้แย้งเป็นตัวแปรที่เราเก็บข้อมูลเกี่ยวกับฟังก์ชั่น
function processArray(arr, callback) {
var resultArr = new Array();
for (var i = arr.length-1; i >= 0; i--)
resultArr[i] = callback(arr[i]);
return resultArr;
}
var arr = [1, 2, 3, 4];
var arrReturned = processArray(arr, function(arg) {return arg * -1;});
// arrReturned would be [-1, -2, -3, -4]
function(arg)
) ในprocessArray(arr,callback)
ฟังก์ชั่น
ฉันจะพยายามทำให้คนตายง่าย ๆ "โทรกลับ" เป็นฟังก์ชั่นใด ๆ ที่ถูกเรียกโดยฟังก์ชั่นอื่นซึ่งใช้ฟังก์ชั่นแรกเป็นพารามิเตอร์ บ่อยครั้งที่ "การโทรกลับ" เป็นฟังก์ชั่นที่ถูกเรียกเมื่อมีบางสิ่งเกิดขึ้น ว่าสิ่งที่สามารถเรียกว่า "เหตุการณ์" ในการเขียนโปรแกรมพูด
ลองนึกภาพสถานการณ์นี้: คุณคาดหวังแพคเกจในอีกสองสามวัน แพคเกจเป็นของขวัญสำหรับเพื่อนบ้านของคุณ ดังนั้นเมื่อคุณได้รับแพ็คเกจคุณต้องนำมันไปให้เพื่อนบ้าน คุณออกนอกเมืองแล้วดังนั้นคุณจึงทิ้งคำแนะนำสำหรับคู่สมรสของคุณ
คุณสามารถบอกพวกเขาเพื่อรับแพคเกจและนำไปให้เพื่อนบ้าน หากคู่สมรสของคุณโง่เหมือนคอมพิวเตอร์พวกเขาจะนั่งที่ประตูและรอพัสดุจนกว่ามันจะมา แต่มีวิธีที่ดีกว่า บอกคู่สมรสของคุณว่าเมื่อพวกเขาได้รับหีบห่อพวกเขาควรนำไปให้เพื่อนบ้าน จากนั้นพวกเขาสามารถใช้ชีวิตตามปกติจนกระทั่งได้รับแพ็คเกจ
ในตัวอย่างของเราการรับแพคเกจคือ "เหตุการณ์" และการนำไปยังเพื่อนบ้านคือ "การโทรกลับ" คู่สมรสของคุณ "รัน" คำแนะนำของคุณเพื่อนำแพคเกจมาเฉพาะเมื่อแพคเกจมาถึง ดีกว่ามาก!
ความคิดแบบนี้เห็นได้ชัดในชีวิตประจำวัน แต่คอมพิวเตอร์ไม่มีสามัญสำนึกแบบเดียวกัน พิจารณาว่าโปรแกรมเมอร์เขียนไฟล์อย่างไร:
fileObject = open(file)
# now that we have WAITED for the file to open, we can write to it
fileObject.write("We are writing to the file.")
# now we can continue doing the other, totally unrelated things our program does
ที่นี่เรารอให้ไฟล์เปิดก่อนที่เราจะเขียนลงไป "บล็อก" การไหลของการดำเนินการและโปรแกรมของเราไม่สามารถทำสิ่งอื่นใดที่อาจต้องทำ! ถ้าเราสามารถทำสิ่งนี้แทน:
# we pass writeToFile (A CALLBACK FUNCTION!) to the open function
fileObject = open(file, writeToFile)
# execution continues flowing -- we don't wait for the file to be opened
# ONCE the file is opened we write to it, but while we wait WE CAN DO OTHER THINGS!
ปรากฎว่าเราทำสิ่งนี้ด้วยภาษาและกรอบบางอย่าง มันเจ๋งมาก! ลองใช้Node.jsเพื่อฝึกฝนจริงด้วยความคิดแบบนี้
open
ทำงาน เป็นไปได้ที่open
บล็อกภายในได้ในขณะที่รอให้ระบบปฏิบัติการทำเวทย์มนตร์ดำ ไม่มีความแตกต่างในผลลัพธ์ในกรณีดังกล่าว
จะอธิบายการโทรกลับเป็นภาษาอังกฤษธรรมดาได้อย่างไร
ในภาษาอังกฤษธรรมดาฟังก์ชั่นการเรียกกลับเป็นเหมือนคนทำงานที่ "โทรกลับ" เพื่อเขาผู้จัดการเมื่อเขาได้เสร็จสิ้นการงาน
พวกเขาแตกต่างจากการเรียกฟังก์ชั่นหนึ่งจากฟังก์ชั่นอื่นที่รับบริบทบางอย่างจากฟังก์ชั่นการโทรได้อย่างไร?
เป็นความจริงที่ว่าคุณกำลังเรียกใช้ฟังก์ชันจากฟังก์ชันอื่น แต่ที่สำคัญคือการโทรกลับนั้นถือเป็นวัตถุดังนั้นคุณจึงสามารถเปลี่ยนฟังก์ชันที่จะเรียกได้ตามสถานะของระบบ (เช่นรูปแบบการออกแบบกลยุทธ์)
พลังของพวกเขาจะถูกอธิบายต่อโปรแกรมเมอร์มือใหม่ได้อย่างไร?
พลังของการเรียกกลับสามารถดูได้ง่ายในเว็บไซต์สไตล์ AJAX ที่ต้องการดึงข้อมูลจากเซิร์ฟเวอร์ การดาวน์โหลดข้อมูลใหม่อาจใช้เวลาสักครู่ ส่วนติดต่อผู้ใช้ทั้งหมดของคุณจะ "หยุด" ในขณะที่ดาวน์โหลดข้อมูลใหม่หรือคุณจะต้องรีเฟรชหน้าทั้งหมดแทนที่จะเป็นเพียงส่วนหนึ่ง ด้วยการโทรกลับคุณสามารถแทรกภาพ "กำลังโหลด" และแทนที่ด้วยข้อมูลใหม่เมื่อมีการโหลด
function grabAndFreeze() {
showNowLoading(true);
var jsondata = getData('http://yourserver.com/data/messages.json');
/* User Interface 'freezes' while getting data */
processData(jsondata);
showNowLoading(false);
do_other_stuff(); // not called until data fully downloaded
}
function processData(jsondata) { // do something with the data
var count = jsondata.results ? jsondata.results.length : 0;
$('#counter_messages').text(['Fetched', count, 'new items'].join(' '));
$('#results_messages').html(jsondata.results || '(no new messages)');
}
นี่คือตัวอย่างที่มีการเรียกกลับโดยใช้ jQuery ฯgetJSON :
function processDataCB(jsondata) { // callback: update UI with results
showNowLoading(false);
var count = jsondata.results ? jsondata.results.length : 0;
$('#counter_messages').text(['Fetched', count, 'new items'].join(' '));
$('#results_messages').html(jsondata.results || '(no new messages)');
}
function grabAndGo() { // and don't freeze
showNowLoading(true);
$('#results_messages').html(now_loading_image);
$.getJSON("http://yourserver.com/data/messages.json", processDataCB);
/* Call processDataCB when data is downloaded, no frozen User Interface! */
do_other_stuff(); // called immediately
}
บ่อยครั้งที่ความต้องการในการเข้าถึงการติดต่อกลับstate
จากฟังก์ชั่นการเรียกใช้closure
ซึ่งเป็นเหมือนคนทำงานจำเป็นต้องได้รับข้อมูลจากผู้จัดการก่อนที่เขาจะสามารถดำเนินการของเขางาน ในการสร้างclosure
คุณสามารถอินไลน์ฟังก์ชันเพื่อดูข้อมูลในบริบทการโทร:
/* Grab messages, chat users, etc by changing dtable. Run callback cb when done.*/
function grab(dtable, cb) {
if (null == dtable) { dtable = "messages"; }
var uiElem = "_" + dtable;
showNowLoading(true, dtable);
$('#results' + uiElem).html(now_loading_image);
$.getJSON("http://yourserver.com/user/"+dtable+".json", cb || function (jsondata) {
// Using a closure: can "see" dtable argument and uiElem variables above.
var count = jsondata.results ? jsondata.results.length : 0,
counterMsg = ['Fetched', count, 'new', dtable].join(' '),
// no new chatters/messages/etc
defaultResultsMsg = ['(no new ', dtable, ')'].join('');
showNowLoading(false, dtable);
$('#counter' + uiElem).text(counterMsg);
$('#results'+ uiElem).html(jsondata.results || defaultResultsMsg);
});
/* User Interface calls cb when data is downloaded */
do_other_stuff(); // called immediately
}
// update results_chatters when chatters.json data is downloaded:
grab("chatters");
// update results_messages when messages.json data is downloaded
grab("messages");
// call myCallback(jsondata) when "history.json" data is loaded:
grab("history", myCallback);
สุดท้ายนี่คือคำนิยามของclosure
จากDouglas Crockford :
ฟังก์ชั่นสามารถกำหนดได้ภายในฟังก์ชั่นอื่น ๆ ฟังก์ชั่นภายในมีการเข้าถึง vars และพารามิเตอร์ของฟังก์ชั่นด้านนอก ถ้าการอ้างอิงถึงฟังก์ชันภายในยังคงอยู่ (ตัวอย่างเช่นในฐานะฟังก์ชันการเรียกกลับ) vars ของฟังก์ชันภายนอกยังสามารถอยู่รอดได้
ดูสิ่งนี้ด้วย:
ฉันตะลึงเมื่อเห็นคนฉลาดจำนวนมากที่ล้มเหลวในการตอกย้ำความจริงที่คำว่า "โทรกลับ" ได้ถูกนำมาใช้ในสองวิธีที่ไม่สอดคล้องกัน
ทั้งสองวิธีเกี่ยวข้องกับการปรับแต่งฟังก์ชั่นโดยการส่งฟังก์ชั่นเพิ่มเติม (การกำหนดฟังก์ชั่น, ไม่ระบุชื่อหรือตั้งชื่อ) ไปยังฟังก์ชั่นที่มีอยู่ กล่าวคือ
customizableFunc(customFunctionality)
หากฟังก์ชันการทำงานที่กำหนดเองนั้นเสียบเข้ากับบล็อคโค้ดคุณได้ปรับแต่งฟังก์ชั่นเช่นนั้น
customizableFucn(customFunctionality) {
var data = doSomthing();
customFunctionality(data);
...
}
แม้ว่าฟังก์ชั่นการฉีดแบบนี้มักจะเรียกว่า "การโทรกลับ" แต่ก็ไม่มีอะไรเกิดขึ้นกับมัน ตัวอย่างที่ชัดเจนมากคือวิธี forEach ซึ่งมีฟังก์ชั่นที่กำหนดเองให้เป็นอาร์กิวเมนต์ที่จะใช้กับแต่ละองค์ประกอบในอาร์เรย์เพื่อปรับเปลี่ยนอาร์เรย์
แต่นี่คือพื้นฐานที่แตกต่างจากการใช้ฟังก์ชั่น "โทรกลับ" สำหรับการเขียนโปรแกรมอะซิงโครนัสเช่นเดียวกับใน AJAX หรือ node.js หรือเพียงแค่การกำหนดฟังก์ชั่นให้กับกิจกรรมการโต้ตอบผู้ใช้ (เช่นการคลิกเมาส์) ในกรณีนี้แนวคิดทั้งหมดคือการรอให้เหตุการณ์เกิดขึ้นก่อนที่จะดำเนินการฟังก์ชันการทำงานที่กำหนดเอง สิ่งนี้ชัดเจนในกรณีของการโต้ตอบกับผู้ใช้ แต่ก็มีความสำคัญในกระบวนการ i / o (อินพุต / เอาต์พุต) ที่อาจต้องใช้เวลาเช่นการอ่านไฟล์จากดิสก์ นี่คือที่คำว่า "โทรกลับ" ทำให้รู้สึกชัดเจนที่สุด เมื่อกระบวนการ i / o เริ่มต้นขึ้น (เช่นขอให้ไฟล์อ่านจากดิสก์หรือเซิร์ฟเวอร์เพื่อส่งคืนข้อมูลจากการร้องขอ HTTP) แบบอะซิงโครนัสโปรแกรมไม่รอให้เสร็จ มันสามารถไปข้างหน้ากับสิ่งที่งานที่กำหนดไว้ต่อไปและตอบสนองกับฟังก์ชั่นที่กำหนดเองเท่านั้นหลังจากที่ได้รับแจ้งว่าการอ่านไฟล์หรือคำขอ http เสร็จสมบูรณ์ (หรือล้มเหลว) และข้อมูลที่มีอยู่ในฟังก์ชั่นที่กำหนดเอง มันเหมือนกับการโทรหาธุรกิจทางโทรศัพท์และปล่อยให้หมายเลข "โทรกลับ" ของคุณดังนั้นพวกเขาจึงสามารถโทรหาคุณได้เมื่อมีคนพร้อมที่จะติดต่อกลับ ดีกว่าแขวนอยู่บนเส้นเพราะใครจะรู้ว่านานแค่ไหนและไม่สามารถเข้าร่วมกิจกรรมอื่น ๆ ได้
การใช้แบบอะซิงโครนัสโดยเนื้อแท้เกี่ยวข้องกับวิธีการฟังสำหรับเหตุการณ์ที่ต้องการ (เช่นความสมบูรณ์ของกระบวนการ i / o) ดังนั้นเมื่อมันเกิดขึ้น (และเมื่อเกิดขึ้น) ฟังก์ชันการเรียกกลับ "แบบกำหนดเอง" จะถูกดำเนินการ ในตัวอย่าง AJAX ที่เห็นได้ชัดเมื่อข้อมูลมาถึงจากเซิร์ฟเวอร์จริง ๆ ฟังก์ชั่น "โทรกลับ" จะถูกเรียกให้ใช้ข้อมูลนั้นเพื่อแก้ไข DOM และดังนั้นจึงวาดหน้าต่างเบราว์เซอร์ขึ้นมาใหม่
เพื่อสรุป บางคนใช้คำว่า "โทรกลับ" เพื่ออ้างถึงฟังก์ชั่นที่กำหนดเองทุกชนิดที่สามารถแทรกลงในฟังก์ชั่นที่มีอยู่เป็นอาร์กิวเมนต์ แต่สำหรับฉันแล้วอย่างน้อยที่สุดการใช้คำที่เหมาะสมที่สุดก็คือฟังก์ชั่น "โทรกลับ" แบบฉีดที่ถูกใช้แบบอะซิงโครนัส - จะถูกดำเนินการเฉพาะเมื่อมีเหตุการณ์ที่กำลังรอการแจ้งเตือนเท่านั้น
Array.prototype.forEach()
และฟังก์ชั่นที่ส่งผ่านเป็น ARG ไปsetTimeout()
และพวกเขาเป็นม้าสีที่แตกต่างกันเท่าที่คุณเหตุผลเกี่ยวกับโปรแกรมของคุณ .
ในเงื่อนไขที่ไม่ใช่โปรแกรมเมอร์การเรียกกลับเป็นแบบเติมคำในโปรแกรม
รายการทั่วไปในหลายรูปแบบกระดาษคือ "บุคคลที่จะโทรในกรณีฉุกเฉิน" มีบรรทัดว่างอยู่ที่นั่น คุณเขียนชื่อและหมายเลขโทรศัพท์ของใครบางคน หากเกิดเหตุฉุกเฉินบุคคลนั้นจะถูกเรียก
นี่คือกุญแจสำคัญ คุณไม่ได้เปลี่ยนรูปแบบ (รหัสมักจะเป็นคนอื่น) อย่างไรก็ตามคุณสามารถกรอกข้อมูลที่ขาดหายไป ( หมายเลขของคุณ )
ตัวอย่างที่ 1:
การโทรกลับถูกใช้เป็นวิธีการที่กำหนดเองซึ่งอาจใช้สำหรับการเพิ่ม / เปลี่ยนพฤติกรรมของโปรแกรม ตัวอย่างเช่นรับรหัส C ที่ทำหน้าที่ฟังก์ชั่น แต่ไม่รู้ว่าจะพิมพ์งานได้อย่างไร สิ่งที่มันสามารถทำได้คือสร้างสตริง เมื่อพยายามหาว่าจะทำอย่างไรกับสตริงมันจะเห็นบรรทัดว่าง แต่โปรแกรมเมอร์ให้คุณว่างเพื่อเขียนโทรกลับของคุณใน!
set_print_callback(the_callback)
ในตัวอย่างนี้คุณไม่ได้ใช้ดินสอกรอกข้อมูลลงในที่ว่างเปล่าบนแผ่นกระดาษที่คุณใช้ฟังก์ชั่น
set_print_callback
เป็นดินสอthe_callback
เป็นข้อมูลของคุณที่คุณกรอกตอนนี้คุณได้กรอกข้อมูลในบรรทัดว่างในโปรแกรม เมื่อใดก็ตามที่ต้องการพิมพ์ผลลัพธ์มันจะดูบรรทัดว่างนั้นและทำตามคำแนะนำที่นั่น (เช่นเรียกใช้ฟังก์ชันที่คุณใส่ไว้) ในทางปฏิบัติสิ่งนี้ช่วยให้สามารถพิมพ์หน้าจอเป็นไฟล์บันทึกไปยังเครื่องพิมพ์ ผ่านการเชื่อมต่อเครือข่ายหรือการรวมกันใด ๆ คุณได้เติมสิ่งที่คุณต้องการทำในช่องว่าง
ตัวอย่างที่ 2:
เมื่อคุณได้รับแจ้งว่าคุณต้องโทรไปยังหมายเลขฉุกเฉินคุณจะไปและอ่านสิ่งที่เขียนในแบบฟอร์มกระดาษแล้วโทรไปยังหมายเลขที่คุณอ่าน หากบรรทัดนั้นว่างเปล่าจะไม่มีการดำเนินการใด ๆ
การเขียนโปรแกรม Gui ทำงานในลักษณะเดียวกัน เมื่อมีการคลิกปุ่มโปรแกรมจำเป็นต้องรู้ว่าจะทำอย่างไรต่อไป มันไปและมองหาการโทรกลับ การเรียกกลับนี้เกิดขึ้นในช่องว่างที่มีข้อความว่า "นี่คือสิ่งที่คุณทำเมื่อมีการคลิกที่ปุ่ม 1"
IDEs ส่วนใหญ่จะเติมช่องว่างให้คุณโดยอัตโนมัติ (เขียนวิธีการพื้นฐาน) เมื่อคุณขอให้ (เช่นbutton1_clicked
) แต่ว่างเปล่าที่สามารถมีวิธีการใด ๆ ที่คุณยี้กันโปรด คุณสามารถเรียกวิธีการrun_computations
หรือbutter_the_biscuits
ตราบเท่าที่คุณใส่ชื่อของการโทรกลับนั้นว่างเปล่าที่เหมาะสม คุณสามารถใส่ "555-555-1212" ในหมายเลขฉุกเฉินที่ว่างเปล่า มันไม่สมเหตุสมผล แต่ก็อนุญาต
หมายเหตุสุดท้าย: บรรทัดว่างที่คุณกรอกด้วยการโทรกลับหรือไม่ สามารถลบและเขียนใหม่ได้ตามต้องการ (ไม่ว่าคุณจะเป็นคำถามอื่นหรือไม่ แต่เป็นส่วนหนึ่งของพลังของพวกเขา)
ดีกว่าเสมอที่จะเริ่มต้นด้วยตัวอย่าง :)
สมมติว่าคุณมีสองโมดูล A และ B
คุณต้องการให้โมดูล A ถูกแจ้งเตือนเมื่อมีเหตุการณ์ / เงื่อนไขบางอย่างเกิดขึ้นในโมดูล B อย่างไรก็ตามโมดูล B ไม่มีความคิดเกี่ยวกับโมดูล A ของคุณสิ่งที่รู้คือที่อยู่ของฟังก์ชันเฉพาะ (ของโมดูล A) ผ่านตัวชี้ฟังก์ชันที่ จัดทำโดยโมดูล A
ดังนั้นสิ่งที่ B ต้องทำตอนนี้คือ "การเรียกกลับ" ในโมดูล A เมื่อเหตุการณ์ / เงื่อนไขเฉพาะเกิดขึ้นโดยใช้ตัวชี้ฟังก์ชัน สามารถทำการประมวลผลเพิ่มเติมภายในฟังก์ชั่นการโทรกลับ
*) ข้อได้เปรียบที่ชัดเจนที่นี่คือคุณสรุปทุกอย่างเกี่ยวกับโมดูล A จากโมดูล B โมดูล B ไม่จำเป็นต้องสนใจว่าใคร / โมดูล A คืออะไร
ลองนึกภาพคุณต้องใช้ฟังก์ชันที่ส่งคืน 10 กำลังสองเพื่อให้คุณเขียนฟังก์ชัน:
function tenSquared() {return 10*10;}
หลังจากนั้นคุณต้องมี 9 กำลังสองเพื่อให้คุณเขียนฟังก์ชันอื่น:
function nineSquared() {return 9*9;}
ในที่สุดคุณจะแทนที่สิ่งเหล่านี้ด้วยฟังก์ชันทั่วไป:
function square(x) {return x*x;}
การคิดแบบเดียวกันนั้นใช้กับการโทรกลับ คุณมีฟังก์ชั่นที่ทำงานบางอย่างและเมื่อโทรออก doA:
function computeA(){
...
doA(result);
}
หลังจากนั้นคุณต้องการฟังก์ชั่นที่เหมือนกันเพื่อเรียกใช้ doB แทนคุณสามารถทำซ้ำฟังก์ชันทั้งหมด:
function computeB(){
...
doB(result);
}
หรือคุณสามารถส่งผ่านฟังก์ชันการโทรกลับเป็นตัวแปรและจะต้องมีฟังก์ชันหนึ่งครั้ง:
function compute(callback){
...
callback(result);
}
จากนั้นคุณต้องเรียกใช้ compute (doA) และ compute (doB)
นอกเหนือจากรหัสที่ทำให้เข้าใจง่ายแล้วมันยังให้รหัสแบบอะซิงโครนัสแจ้งให้คุณทราบว่ามันเสร็จสมบูรณ์โดยการเรียกฟังก์ชั่นโดยพลการของคุณเมื่อทำเสร็จคล้ายกับเมื่อคุณโทรหาใครบางคนทางโทรศัพท์และออกหมายเลขโทรกลับ
Johny โปรแกรมเมอร์ต้องการเครื่องเย็บกระดาษดังนั้นเขาจึงลงไปที่แผนกเครื่องใช้สำนักงานและขอหนึ่งเครื่องหลังจากกรอกแบบฟอร์มคำขอเขาสามารถยืนที่นั่นและรอให้เสมียนไปดูรอบ ๆ คลังสินค้าสำหรับเครื่องเย็บกระดาษ (เช่นการเรียกฟังก์ชันปิดกั้น ) หรือไปทำอย่างอื่นระหว่างนี้
ตั้งแต่นี้มักจะใช้เวลา johny วางบันทึกพร้อมกับแบบฟอร์มขอให้พวกเขาโทรหาเขาเมื่อเครื่องเย็บกระดาษพร้อมสำหรับการหยิบดังนั้นในขณะเดียวกันเขาสามารถไปทำสิ่งอื่นเช่นนอนหลับบนโต๊ะของเขา
คุณรู้สึกไม่สบายคุณจึงไปหาหมอ เขาตรวจสอบคุณและกำหนดว่าคุณต้องการยาอะไรบ้าง เขากำหนดยาและเรียกใบสั่งยาในร้านขายยาในพื้นที่ของคุณ คุณกลับบ้าน. หลังจากนั้นร้านขายยาของคุณโทรมาเพื่อบอกคุณว่าใบสั่งยาของคุณพร้อมแล้ว คุณไปรับมัน
มีสองประเด็นที่จะอธิบายหนึ่งคือวิธีการโทรกลับทำงาน (ผ่านรอบฟังก์ชั่นที่สามารถเรียกได้โดยไม่ต้องรู้บริบทของมัน), อื่น ๆ สิ่งที่มันใช้สำหรับ (การจัดการเหตุการณ์แบบอะซิงโครนัส)
การเปรียบเทียบการรอพัสดุมาถึงที่มีการใช้โดยคำตอบอื่น ๆ เป็นสิ่งที่ดีที่จะอธิบายทั้ง ในโปรแกรมคอมพิวเตอร์คุณจะบอกให้คอมพิวเตอร์คาดหวังพัสดุ ตามปกติตอนนี้มันจะนั่งอยู่ที่นั่นและรอ (และไม่ทำอะไรเลย) จนกว่าพัสดุจะมาถึงอาจเป็นไปเรื่อย ๆ หากไม่เคยมาถึง สำหรับมนุษย์นี่ฟังดูงี่เง่า แต่ไม่มีมาตรการเพิ่มเติมนี่เป็นเรื่องธรรมชาติสำหรับคอมพิวเตอร์
ตอนนี้การติดต่อกลับจะเป็นเสียงระฆังที่ประตูหน้าของคุณ คุณให้บริการพัสดุด้วยวิธีที่จะแจ้งให้คุณทราบถึงการมาถึงของพัสดุโดยที่พวกเขาไม่ต้องรู้ว่าคุณอยู่ที่ไหนในบ้าน (แม้ว่าจะ) หรือว่าระฆังทำงานอย่างไร (ตัวอย่างเช่น "ระฆัง" บางคนส่งสายจริง) เนื่องจากคุณได้จัดเตรียม "ฟังก์ชันการโทรกลับ" ที่สามารถ "เรียก" ได้ทุกเวลานอกบริบทคุณสามารถหยุดนั่งที่ระเบียงด้านหน้าและ "จัดการกับ เหตุการณ์ "(ของการมาถึงของพัสดุ) เมื่อใดก็ตามที่ถึงเวลา
ลองนึกภาพเพื่อนกำลังออกจากบ้านของคุณและคุณบอกเธอว่า "โทรหาฉันเมื่อคุณกลับถึงบ้านเพื่อให้ฉันรู้ว่าคุณมาถึงอย่างปลอดภัย"; ที่เป็น (ตัวอักษร) กกลับโทร นั่นคือสิ่งที่ฟังก์ชั่นการโทรกลับโดยไม่คำนึงถึงภาษา คุณต้องการให้ขั้นตอนบางอย่างผ่านการควบคุมกลับไปที่คุณเมื่อมันเสร็จสิ้นงานดังนั้นคุณจึงให้ฟังก์ชันที่จะใช้ในการโทรกลับหาคุณ
ใน Python เช่น
grabDBValue( (lambda x: passValueToGUIWindow(x) ))
grabDBValue
สามารถเขียนได้เฉพาะคว้าค่าจากฐานข้อมูลจากนั้นให้คุณระบุสิ่งที่ต้องทำจริง ๆ กับค่าดังนั้นจึงยอมรับฟังก์ชั่น คุณไม่รู้ว่าgrabDBValue
จะกลับมาเมื่อไรหรืออย่างไรแต่ถ้าเป็นเช่นนั้นคุณรู้ว่าคุณต้องการให้มันทำอะไร ที่นี่ฉันผ่านในฟังก์ชั่นที่ไม่ระบุชื่อ (หรือแลมบ์ดา ) ที่ส่งค่าไปยังหน้าต่าง GUI ฉันสามารถเปลี่ยนพฤติกรรมของโปรแกรมได้อย่างง่ายดายโดยทำสิ่งนี้:
grabDBValue( (lambda x: passToLogger(x) ))
การเรียกกลับทำงานได้ดีในภาษาที่ฟังก์ชันมีค่าคลาสเช่นเดียวกับจำนวนเต็มปกติสตริงอักขระบูลีนและอื่น ๆ ใน C คุณสามารถ "ส่ง" ฟังก์ชันรอบ ๆ ได้โดยการส่งตัวชี้ไปยังผู้เรียก ใน Java ผู้เรียกจะถามคลาสคงที่ของประเภทที่มีชื่อวิธีการบางอย่างเนื่องจากไม่มีฟังก์ชั่น ("วิธีการ" จริงๆ) นอกชั้นเรียน; และในภาษาไดนามิกอื่น ๆ ส่วนใหญ่คุณสามารถส่งผ่านฟังก์ชั่นที่มีไวยากรณ์ง่าย ๆ
ในภาษาที่มีการกำหนดขอบเขตศัพท์ (เช่น Scheme หรือ Perl) คุณสามารถใช้เล่ห์เหลี่ยมเช่นนี้:
my $var = 2;
my $val = someCallerBackFunction(sub callback { return $var * 3; });
# Perlistas note: I know the sub doesn't need a name, this is for illustration
$val
ในกรณีนี้จะเป็น6
เพราะการเรียกกลับมีการเข้าถึงตัวแปรที่ประกาศในสภาพแวดล้อมคำที่มันถูกกำหนดไว้ ขอบเขตคำศัพท์และการเรียกกลับที่ไม่ระบุชื่อเป็นชุดค่าผสมที่มีประสิทธิภาพรับประกันการศึกษาเพิ่มเติมสำหรับโปรแกรมเมอร์สามเณร
คุณมีรหัสที่คุณต้องการเรียกใช้ โดยปกติเมื่อคุณเรียกว่าคุณกำลังรอให้เสร็จก่อนที่จะดำเนินการต่อ (ซึ่งอาจทำให้แอปของคุณเป็นสีเทา / ทำให้เวลาเคอร์เซอร์หมุน)
วิธีอื่นคือการเรียกใช้รหัสนี้ในแบบคู่ขนานและดำเนินการกับงานของคุณเอง แต่ถ้ารหัสต้นฉบับของคุณจำเป็นต้องทำสิ่งต่าง ๆ โดยขึ้นอยู่กับการตอบสนองจากโค้ดที่เรียกว่า ในกรณีนี้คุณสามารถส่งผ่านชื่อ / ที่ตั้งของรหัสที่คุณต้องการให้โทรเมื่อทำเสร็จ นี่คือ "โทรกลับ"
รหัสปกติ: ขอข้อมูล -> ข้อมูลกระบวนการ -> จัดการกับผลลัพธ์ของการประมวลผล -> ทำสิ่งอื่น ๆ ต่อไป
ด้วยการเรียกกลับ: ขอข้อมูล -> ข้อมูลกระบวนการ -> ทำสิ่งอื่นต่อไป และในเวลาต่อมา -> จัดการกับผลลัพธ์ของการประมวลผล
โดยไม่ต้องโทรกลับค่าอื่น ๆ ทรัพยากรพิเศษการเขียนโปรแกรม (เช่นเกลียว, และอื่น ๆ ) โปรแกรมนั้นตรงกับลำดับของคำสั่งที่จะดำเนินการตามลำดับหนึ่งหลังจากที่อื่นและแม้จะมีชนิดของ "พฤติกรรมแบบไดนามิก" กำหนดโดยเงื่อนไขบางอย่างซึ่งเป็นสถานการณ์ที่เป็นไปได้ทั้งหมด จะถูกตั้งโปรแกรมไว้ก่อนหน้านี้
ดังนั้นหากเราต้องการมอบพฤติกรรมแบบไดนามิกที่แท้จริงให้กับโปรแกรมเราสามารถใช้การติดต่อกลับ ด้วยการเรียกกลับคุณสามารถสั่งให้พารามิเตอร์โปรแกรมเรียกโปรแกรมอื่นที่ให้พารามิเตอร์ที่กำหนดไว้ก่อนหน้านี้และสามารถคาดหวังผลลัพธ์บางอย่าง ( นี่คือสัญญาหรือลายเซ็นการดำเนินการ ) ดังนั้นผลลัพธ์เหล่านี้สามารถผลิต / ประมวลผลโดยโปรแกรมของบุคคลที่สาม ไม่เคยรู้จักมาก่อน
เทคนิคนี้เป็นพื้นฐานของความหลากหลายที่นำไปใช้กับโปรแกรมฟังก์ชั่นวัตถุและความเป็นเอกภาพอื่น ๆ ของรหัสที่ใช้งานโดยคอมพิวเตอร์
โลกมนุษย์ที่ใช้เป็นตัวอย่างในการติดต่อกลับเป็นคำอธิบายที่ดีเมื่อคุณทำงานบางอย่างให้สมมติว่าคุณเป็นจิตรกร ( ที่นี่คุณเป็นโปรแกรมหลักวาดภาพนั้น ) และโทรหาลูกค้าของคุณในบางครั้งเพื่อขอให้เขาอนุมัติผลงานของคุณ ดังนั้นเขาตัดสินใจว่ารูปภาพดีหรือไม่ ( ลูกค้าของคุณเป็นโปรแกรมบุคคลที่สาม )
ในตัวอย่างข้างต้นคุณเป็นจิตรกรและ "มอบหมาย" ให้ผู้อื่นงานอนุมัติผลรูปภาพเป็นพารามิเตอร์และลูกค้าใหม่ (เรียกว่า "ฟังก์ชั่น" เรียกกลับ) แต่ละคนเปลี่ยนผลลัพธ์ของงานของคุณตัดสินใจสิ่งที่เขาต้องการ เกี่ยวกับรูปภาพ ( การตัดสินใจของลูกค้าเป็นผลลัพธ์ที่ส่งคืนจาก "ฟังก์ชันการเรียกกลับ" )
ฉันหวังว่าคำอธิบายนี้จะเป็นประโยชน์
สมมติว่าคุณกำลังจะให้ฉันทำงานที่อาจเกิดขึ้นในระยะยาว: รับชื่อของคนห้าคนแรกที่คุณเจอ อาจใช้เวลาหลายวันถ้าฉันอยู่ในพื้นที่ที่มีประชากรเบาบาง คุณไม่สนใจนั่งอยู่ในมือของคุณในขณะที่ฉันกำลังวิ่งไปรอบ ๆ คุณจึงพูดว่า "เมื่อคุณมีรายชื่อแล้วโทรหาฉันที่เซลล์ของฉันแล้วอ่านกลับมาหาฉันนี่คือหมายเลข"
คุณได้ให้การอ้างอิงการโทรกลับ - ฟังก์ชั่นที่ฉันควรจะดำเนินการเพื่อส่งมอบการประมวลผลเพิ่มเติม
ใน JavaScript อาจมีลักษณะเช่นนี้:
var lottoNumbers = [];
var callback = function(theNames) {
for (var i=0; i<theNames.length; i++) {
lottoNumbers.push(theNames[i].length);
}
};
db.executeQuery("SELECT name " +
"FROM tblEveryOneInTheWholeWorld " +
"ORDER BY proximity DESC " +
"LIMIT 5", callback);
while (lottoNumbers.length < 5) {
playGolf();
}
playLotto(lottoNumbers);
สิ่งนี้อาจปรับปรุงได้หลายวิธี เช่นคุณสามารถโทรกลับครั้งที่สองได้: หากใช้เวลานานกว่าหนึ่งชั่วโมงให้โทรไปที่โทรศัพท์สีแดงแล้วบอกคนที่ตอบว่าคุณหมดเวลาแล้ว
คำอธิบายการโทรกลับนั้นง่ายที่สุดในแง่ของระบบโทรศัพท์ ฟังก์ชั่นการโทรนั้นคล้ายคลึงกับการโทรหาใครบางคนทางโทรศัพท์ถามคำถามรับคำตอบและวางสาย การเพิ่มการโทรกลับจะเปลี่ยนการเปรียบเทียบดังนั้นหลังจากถามคำถามคุณคุณจะให้ชื่อและหมายเลขของเธอกับเธอเพื่อให้เธอสามารถโทรกลับหาคุณด้วยคำตอบ - Paul Jakubik "การดำเนินการติดต่อกลับใน C ++"
การเรียกกลับเป็นฟังก์ชั่นที่จะถูกเรียกโดยฟังก์ชั่นที่สอง ฟังก์ชั่นที่สองนี้ไม่ทราบล่วงหน้าว่าจะมีฟังก์ชั่นอะไร ดังนั้นตัวตนของฟังก์ชั่นการโทรกลับจะถูกเก็บไว้ที่ใดที่หนึ่งหรือส่งผ่านไปยังฟังก์ชั่นที่สองเป็นพารามิเตอร์ "identity" นี้ขึ้นอยู่กับภาษาการเขียนโปรแกรมอาจเป็นที่อยู่ของการติดต่อกลับหรือตัวชี้ประเภทอื่น ๆ หรืออาจเป็นชื่อของฟังก์ชัน ตัวการเหมือนกันเราเก็บหรือส่งข้อมูลบางอย่างที่ระบุฟังก์ชันอย่างไม่น่าสงสัย
เมื่อถึงเวลาฟังก์ชั่นที่สองสามารถโทรกลับได้โดยระบุพารามิเตอร์ขึ้นอยู่กับสถานการณ์ในขณะนั้น มันอาจเลือกการโทรกลับจากชุดการโทรกลับที่เป็นไปได้ ภาษาการเขียนโปรแกรมต้องมีไวยากรณ์บางอย่างเพื่อให้ฟังก์ชันที่สองสามารถโทรกลับได้โดยรู้ว่า "ตัวตน"
กลไกนี้มีประโยชน์มากมาย ด้วยการเรียกกลับผู้ออกแบบฟังก์ชั่นสามารถให้มันปรับแต่งได้โดยให้มันเรียกสิ่งที่มีการเรียกกลับ ตัวอย่างเช่นฟังก์ชั่นการเรียงลำดับอาจใช้การเรียกกลับเป็นพารามิเตอร์และการเรียกกลับนี้อาจเป็นฟังก์ชันสำหรับการเปรียบเทียบสององค์ประกอบเพื่อตัดสินใจว่าจะใช้องค์ประกอบใดมาก่อน
ทั้งนี้ขึ้นอยู่กับภาษาการเขียนโปรแกรมคำว่า "ฟังก์ชั่น" ในการสนทนาข้างต้นอาจถูกแทนที่ด้วย "บล็อก" "ปิด" "แลมบ์ดา" ฯลฯ
เรามักจะส่งตัวแปรไปยังฟังก์ชั่น สมมติว่าคุณมีงานที่ต้องประมวลผลตัวแปรก่อนที่จะได้รับเป็นอาร์กิวเมนต์ - คุณสามารถใช้โทรกลับ
function1(var1, var2)
เป็นวิธีปกติ
ถ้าฉันต้องการvar2
ประมวลผลแล้วส่งเป็นอาร์กิวเมนต์?
function1(var1, function2(var2))
นี่คือการเรียกกลับหนึ่งประเภท - ที่function2
เรียกใช้งานโค้ดบางส่วนและส่งกลับตัวแปรกลับไปที่ฟังก์ชันเริ่มต้น
คำอธิบายเชิงเปรียบเทียบ:
ฉันมีพัสดุที่ฉันต้องการส่งถึงเพื่อนและฉันก็ต้องการทราบเมื่อเพื่อนของฉันได้รับ
ดังนั้นฉันจึงนำพัสดุไปที่ที่ทำการไปรษณีย์และขอให้พวกเขาส่งมอบ หากฉันต้องการทราบว่าเมื่อเพื่อนของฉันรับพัสดุฉันมีสองตัวเลือก:
(a) ฉันสามารถรอที่ไปรษณีย์จนกว่าจะมีการจัดส่ง
(b) ฉันจะได้รับอีเมลเมื่อมีการจัดส่ง
ตัวเลือก (b) คล้ายกับการโทรกลับ
สำหรับการสอนการเรียกกลับคุณต้องสอนตัวชี้ก่อน เมื่อนักเรียนเข้าใจแนวคิดของตัวชี้ไปยังตัวแปรแล้วแนวคิดของการเรียกกลับจะง่ายขึ้น สมมติว่าคุณใช้ C / C ++ ขั้นตอนเหล่านี้สามารถติดตามได้
อาจมีหลายสิ่งหลายอย่าง เกี่ยวข้องกับนักเรียนและพวกเขาจะค้นพบ หวังว่านี่จะช่วยได้
ในภาษาอังกฤษธรรมดาการติดต่อกลับเป็นสัญญา Joe, Jane, David และ Samantha แบ่งปันเวรเพื่อทำงาน โจกำลังขับรถวันนี้ Jane, David และ Samantha มีสองตัวเลือก:
ตัวเลือกที่ 1: นี่เป็นเหมือนตัวอย่างการสำรวจที่ Jane จะติดอยู่ในการตรวจสอบ "ลูป" ถ้าโจอยู่ข้างนอก เจนไม่สามารถทำสิ่งใดได้ในเวลาเดียวกัน
ตัวเลือก 2: นี่คือตัวอย่างการเรียกกลับ เจนบอกให้โจสั่นกระดิ่งเมื่อเขาออกไปข้างนอก เธอให้เขา "ฟังก์ชั่น" เพื่อกดกริ่งประตู โจไม่จำเป็นต้องรู้ว่ากระดิ่งประตูทำงานอย่างไรหรืออยู่ที่ไหนเขาแค่ต้องการเรียกฟังก์ชั่นนั้นเช่นกดกริ่งที่ประตูเมื่อเขาอยู่ที่นั่น
การโทรกลับถูกขับเคลื่อนโดย "กิจกรรม" ในตัวอย่างนี้ "เหตุการณ์" คือการมาถึงของโจ ในอาแจ็กซ์เช่นเหตุการณ์อาจเป็น "ความสำเร็จ" หรือ "ความล้มเหลว" ของการร้องขอแบบอะซิงโครนัสและแต่ละแบบสามารถมีการโทรกลับที่เหมือนกันหรือต่างกัน
ในแง่ของการใช้งานจาวาสคริปต์และการโทรกลับ เราต้องเข้าใจ "การปิด" และบริบทของแอปพลิเคชันด้วย สิ่งนี้หมายถึงอะไรสามารถสับสนนักพัฒนา JavaScript ได้อย่างง่ายดาย ในตัวอย่างนี้ภายใน "ring_the_door_bell ()" วิธีการ / การโทรกลับอาจมีวิธีการอื่นที่แต่ละคนต้องทำตามขั้นตอนการทำงานในตอนเช้า "ปิดทีวี()". เราต้องการให้ "นี่" อ้างถึงวัตถุ "Jane" หรือวัตถุ "David" เพื่อให้แต่ละคนสามารถตั้งค่าสิ่งที่พวกเขาต้องทำก่อนที่ Joe จะหยิบมันขึ้นมา นี่คือจุดที่การตั้งค่าการติดต่อกลับด้วย Joe ต้องใช้วิธีการ parodying เพื่อให้ "นี่" อ้างอิงถึงวัตถุที่ถูกต้อง
หวังว่าจะช่วย!
การติดต่อกลับเป็นซองจดหมายที่ประทับตราด้วยตนเอง เมื่อคุณเรียกใช้ฟังก์ชันนั่นก็เหมือนกับการส่งจดหมาย หากคุณต้องการให้ฟังก์ชันนั้นเรียกใช้ฟังก์ชันอื่นคุณต้องระบุข้อมูลนั้นในรูปแบบของการอ้างอิงหรือที่อยู่
ฉันคิดว่ามันค่อนข้างง่ายที่จะอธิบาย
เมื่อโทรกลับครั้งแรกเป็นเพียงฟังก์ชั่นธรรมดา
และยิ่งไปกว่านั้นคือเราเรียกฟังก์ชันนี้ (เรียกมันว่า A) จากภายในฟังก์ชันอื่น (เรียกมันว่า B)
มหัศจรรย์เกี่ยวกับเรื่องนี้ก็คือว่าผมตัดสินใจที่ฟังก์ชั่นควรจะเรียกว่าฟังก์ชั่นจากภายนอกบี
ในขณะที่ฉันเขียนฟังก์ชัน BI ไม่รู้ว่าควรเรียกใช้ฟังก์ชันการโทรกลับแบบใด ในเวลาที่ฉันเรียกใช้ฟังก์ชัน BI บอกให้ฟังก์ชั่นนี้เรียกใช้ฟังก์ชัน A นั่นคือทั้งหมด
ฟังก์ชั่นโทรกลับคืออะไร?
คำตอบง่ายๆสำหรับคำถามแรกนี้คือฟังก์ชันการเรียกกลับเป็นฟังก์ชันที่เรียกใช้ผ่านตัวชี้ฟังก์ชัน หากคุณผ่านตัวชี้ (ที่อยู่) ของฟังก์ชั่นเป็นอาร์กิวเมนต์ไปยังอีกเมื่อตัวชี้นั้นจะใช้ในการเรียกฟังก์ชั่นมันจะชี้ไปที่มันจะมีการกล่าวว่าการโทรกลับจะทำ
ฟังก์ชั่นการโทรกลับหายาก แต่บางครั้งมันก็มีประโยชน์มาก โดยเฉพาะอย่างยิ่งเมื่อคุณออกแบบห้องสมุด ฟังก์ชั่นการโทรกลับเป็นเหมือนการขอให้ผู้ใช้ของคุณให้ชื่อฟังก์ชั่นและคุณจะเรียกฟังก์ชั่นนั้นภายใต้เงื่อนไขที่แน่นอน
ตัวอย่างเช่นคุณเขียนตัวจับเวลาการโทรกลับ จะช่วยให้คุณระบุระยะเวลาและฟังก์ชั่นการโทรและฟังก์ชั่นจะโทรกลับตามนั้น “ Run myfunction () ทุก ๆ 10 วินาทีเป็นเวลา 5 ครั้ง”
หรือคุณสามารถสร้างไดเรกทอรีฟังก์ชันส่งรายการชื่อฟังก์ชั่นและขอให้ห้องสมุดติดต่อกลับ “ การโทรกลับสำเร็จ () หากสำเร็จโทรกลับล้มเหลว () หากล้มเหลว”
ให้ดูตัวอย่างตัวชี้ฟังก์ชันอย่างง่าย
void cbfunc()
{
printf("called");
}
int main ()
{
/* function pointer */
void (*callback)(void);
/* point to your callback function */
callback=(void *)cbfunc;
/* perform callback */
callback();
return 0;
}
วิธีการส่งผ่านข้อโต้แย้งไปยังฟังก์ชั่นการโทรกลับ?
สังเกตว่าตัวชี้ฟังก์ชั่นที่จะใช้โทรกลับใช้ใน void * ซึ่งบ่งชี้ว่าสามารถใช้กับตัวแปรชนิดใดก็ได้รวมถึงโครงสร้าง ดังนั้นคุณสามารถผ่านหลายอาร์กิวเมนต์ตามโครงสร้าง
typedef struct myst
{
int a;
char b[10];
}myst;
void cbfunc(myst *mt)
{
fprintf(stdout,"called %d %s.",mt->a,mt->b);
}
int main()
{
/* func pointer */
void (*callback)(void *); //param
myst m;
m.a=10;
strcpy(m.b,"123");
callback = (void*)cbfunc; /* point to callback function */
callback(&m); /* perform callback and pass in the param */
return 0;
}
การเรียกกลับเป็นวิธีที่มีกำหนดที่จะดำเนินการเมื่อตรงตามเงื่อนไข
ตัวอย่าง "โลกแห่งความจริง" คือร้านวิดีโอเกมในพื้นที่ คุณกำลังรอ Half-Life 3 แทนที่จะไปที่ร้านทุกวันเพื่อดูว่ามันมีอยู่หรือไม่คุณลงทะเบียนอีเมลของคุณในรายการเพื่อรับการแจ้งเตือนเมื่อเกมพร้อมใช้งาน อีเมลดังกล่าวจะกลายเป็น "การติดต่อกลับ" ของคุณและเงื่อนไขที่ต้องทำคือความพร้อมของเกม
ตัวอย่าง "โปรแกรมเมอร์" คือหน้าเว็บที่คุณต้องการดำเนินการเมื่อมีการคลิกปุ่ม คุณลงทะเบียนวิธีการโทรกลับสำหรับปุ่มและทำงานอื่น ๆ ต่อไป เมื่อ / ถ้าผู้ใช้กดปุ่มเบราว์เซอร์จะดูรายการการเรียกกลับสำหรับเหตุการณ์นั้นและเรียกวิธีการของคุณ
การเรียกกลับเป็นวิธีจัดการเหตุการณ์แบบอะซิงโครนัส คุณไม่มีทางรู้ว่าจะทำการโทรกลับเมื่อใดหรือจะดำเนินการใด ๆ ข้อดีคือมันช่วยให้โปรแกรมและรอบการทำงานของ CPU ของคุณทำงานได้อย่างเต็มที่ในขณะที่รอการตอบกลับ
ธรรมดาและเรียบง่าย: การเรียกกลับเป็นฟังก์ชั่นที่คุณมอบให้กับฟังก์ชั่นอื่นเพื่อที่จะสามารถเรียกมันได้
โดยปกติจะเรียกว่าเมื่อการดำเนินการบางอย่างเสร็จสมบูรณ์ เนื่องจากคุณสร้างการเรียกกลับก่อนที่จะให้ฟังก์ชันอื่นคุณสามารถเริ่มต้นได้ด้วยข้อมูลบริบทจากไซต์การโทร นั่นคือสาเหตุที่ชื่อการโทร * ย้อนกลับ * - ฟังก์ชั่นแรกโทรกลับสู่บริบทจากที่มันถูกเรียก
“ ในการเขียนโปรแกรมคอมพิวเตอร์การเรียกกลับเป็นการอ้างอิงถึงรหัสที่ใช้งานได้หรือส่วนหนึ่งของรหัสที่สามารถเรียกทำงานได้ซึ่งจะถูกส่งเป็นอาร์กิวเมนต์ไปยังรหัสอื่น สิ่งนี้อนุญาตให้เลเยอร์ซอฟต์แวร์ระดับต่ำกว่าเรียกรูทีนย่อย (หรือฟังก์ชัน) ที่กำหนดในเลเยอร์ระดับสูงกว่า” - วิกิพีเดีย
โทรกลับใน C โดยใช้ฟังก์ชั่นตัวชี้
ใน C การเรียกกลับถูกนำไปใช้โดยใช้ Function Pointer ตัวชี้ฟังก์ชั่น - ตามชื่อแนะนำเป็นตัวชี้ไปยังฟังก์ชั่น
ตัวอย่างเช่น int (* ptrFunc) ();
ที่นี่ ptrFunc เป็นตัวชี้ไปยังฟังก์ชั่นที่ไม่มีข้อโต้แย้งและส่งกลับจำนวนเต็ม อย่าลืมใส่ในวงเล็บมิฉะนั้นคอมไพเลอร์จะถือว่า ptrFunc เป็นชื่อฟังก์ชั่นปกติซึ่งไม่ทำอะไรเลยและส่งกลับตัวชี้เป็นจำนวนเต็ม
นี่คือรหัสบางส่วนเพื่อแสดงให้เห็นถึงตัวชี้ฟังก์ชั่น
#include<stdio.h>
int func(int, int);
int main(void)
{
int result1,result2;
/* declaring a pointer to a function which takes
two int arguments and returns an integer as result */
int (*ptrFunc)(int,int);
/* assigning ptrFunc to func's address */
ptrFunc=func;
/* calling func() through explicit dereference */
result1 = (*ptrFunc)(10,20);
/* calling func() through implicit dereference */
result2 = ptrFunc(10,20);
printf("result1 = %d result2 = %d\n",result1,result2);
return 0;
}
int func(int x, int y)
{
return x+y;
}
ตอนนี้ให้เราพยายามที่จะเข้าใจแนวคิดของการโทรกลับใน C โดยใช้ตัวชี้ฟังก์ชั่น
โปรแกรมที่สมบูรณ์มีสามไฟล์: callback.c, reg_callback.h และ reg_callback.c
/* callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* callback function definition goes here */
void my_callback(void)
{
printf("inside my_callback\n");
}
int main(void)
{
/* initialize function pointer to
my_callback */
callback ptr_my_callback=my_callback;
printf("This is a program demonstrating function callback\n");
/* register our callback function */
register_callback(ptr_my_callback);
printf("back inside main program\n");
return 0;
}
/* reg_callback.h */
typedef void (*callback)(void);
void register_callback(callback ptr_reg_callback);
/* reg_callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* registration goes here */
void register_callback(callback ptr_reg_callback)
{
printf("inside register_callback\n");
/* calling our callback function my_callback */
(*ptr_reg_callback)();
}
ถ้าเรารันโปรแกรมนี้ผลลัพธ์จะเป็น
นี่คือโปรแกรมที่แสดงฟังก์ชันการเรียกกลับภายใน register_callback ด้านใน my_callback ด้านหลังในโปรแกรมหลัก
ฟังก์ชันเลเยอร์ที่สูงกว่าเรียกฟังก์ชันเลเยอร์ที่ต่ำกว่าเป็นการโทรปกติและกลไกการเรียกกลับช่วยให้ฟังก์ชันเลเยอร์ที่ต่ำกว่าเรียกฟังก์ชันเลเยอร์ที่สูงขึ้นผ่านตัวชี้ไปยังฟังก์ชันการเรียกกลับ
การโทรกลับใน Java โดยใช้อินเตอร์เฟส
Java ไม่ได้มีแนวคิดของตัวชี้ฟังก์ชั่นมันใช้กลไกการโทรกลับผ่านกลไกการติดต่อที่นี่แทนที่จะเป็นตัวชี้ฟังก์ชั่นเราประกาศอินเตอร์เฟซที่มีวิธีการซึ่งจะถูกเรียกเมื่อ callee เสร็จสิ้นภารกิจ
ให้ฉันสาธิตผ่านตัวอย่าง:
อินเตอร์เฟสการติดต่อกลับ
public interface Callback
{
public void notify(Result result);
}
ผู้เรียกหรือระดับที่สูงขึ้น
public Class Caller implements Callback
{
Callee ce = new Callee(this); //pass self to the callee
//Other functionality
//Call the Asynctask
ce.doAsynctask();
public void notify(Result result){
//Got the result after the callee has finished the task
//Can do whatever i want with the result
}
}
ฟังก์ชัน Callee หรือฟังก์ชันเลเยอร์ที่ต่ำกว่า
public Class Callee {
Callback cb;
Callee(Callback cb){
this.cb = cb;
}
doAsynctask(){
//do the long running task
//get the result
cb.notify(result);//after the task is completed, notify the caller
}
}
การโทรกลับโดยใช้รูปแบบ EventListener
รูปแบบนี้ใช้เพื่อแจ้งจำนวนผู้สังเกตการณ์ / ผู้ฟังจำนวน 0 ถึง n ว่างานเฉพาะได้เสร็จสิ้นแล้ว
ความแตกต่างระหว่างกลไกการโทรกลับและกลไก EventListener / Observer คือในการติดต่อกลับผู้แจ้งเตือนจะแจ้งผู้โทรเพียงคนเดียวในขณะที่ผู้จัดกิจกรรม / ผู้สังเกตการณ์ผู้รับสามารถแจ้งผู้ที่สนใจเหตุการณ์นั้นได้ (การแจ้งเตือนอาจไปยังส่วนอื่น ๆ ของ แอปพลิเคชั่นที่ไม่ได้เรียกใช้งาน
ให้ฉันอธิบายผ่านตัวอย่าง
อินเทอร์เฟซเหตุการณ์
public interface Events {
public void clickEvent();
public void longClickEvent();
}
วิดเจ็ตชั้นเรียน
package com.som_itsolutions.training.java.exampleeventlistener;
import java.util.ArrayList;
import java.util.Iterator;
public class Widget implements Events{
ArrayList<OnClickEventListener> mClickEventListener = new ArrayList<OnClickEventListener>();
ArrayList<OnLongClickEventListener> mLongClickEventListener = new ArrayList<OnLongClickEventListener>();
@Override
public void clickEvent() {
// TODO Auto-generated method stub
Iterator<OnClickEventListener> it = mClickEventListener.iterator();
while(it.hasNext()){
OnClickEventListener li = it.next();
li.onClick(this);
}
}
@Override
public void longClickEvent() {
// TODO Auto-generated method stub
Iterator<OnLongClickEventListener> it = mLongClickEventListener.iterator();
while(it.hasNext()){
OnLongClickEventListener li = it.next();
li.onLongClick(this);
}
}
public interface OnClickEventListener
{
public void onClick (Widget source);
}
public interface OnLongClickEventListener
{
public void onLongClick (Widget source);
}
public void setOnClickEventListner(OnClickEventListener li){
mClickEventListener.add(li);
}
public void setOnLongClickEventListner(OnLongClickEventListener li){
mLongClickEventListener.add(li);
}
}
ปุ่มคลาส
public class Button extends Widget{
private String mButtonText;
public Button (){
}
public String getButtonText() {
return mButtonText;
}
public void setButtonText(String buttonText) {
this.mButtonText = buttonText;
}
}
ช่องทำเครื่องหมายคลาส
public class CheckBox extends Widget{
private boolean checked;
public CheckBox() {
checked = false;
}
public boolean isChecked(){
return (checked == true);
}
public void setCheck(boolean checked){
this.checked = checked;
}
}
ชั้นเรียนกิจกรรม
แพ็คเกจ com.som_itsolutions.training.java.exampleeventlistener;
public class Activity implements Widget.OnClickEventListener
{
public Button mButton;
public CheckBox mCheckBox;
private static Activity mActivityHandler;
public static Activity getActivityHandle(){
return mActivityHandler;
}
public Activity ()
{
mActivityHandler = this;
mButton = new Button();
mButton.setOnClickEventListner(this);
mCheckBox = new CheckBox();
mCheckBox.setOnClickEventListner(this);
}
public void onClick (Widget source)
{
if(source == mButton){
mButton.setButtonText("Thank you for clicking me...");
System.out.println(((Button) mButton).getButtonText());
}
if(source == mCheckBox){
if(mCheckBox.isChecked()==false){
mCheckBox.setCheck(true);
System.out.println("The checkbox is checked...");
}
else{
mCheckBox.setCheck(false);
System.out.println("The checkbox is not checked...");
}
}
}
public void doSomeWork(Widget source){
source.clickEvent();
}
}
ชั้นอื่น ๆ
public class OtherClass implements Widget.OnClickEventListener{
Button mButton;
public OtherClass(){
mButton = Activity.getActivityHandle().mButton;
mButton.setOnClickEventListner(this);//interested in the click event //of the button
}
@Override
public void onClick(Widget source) {
if(source == mButton){
System.out.println("Other Class has also received the event notification...");
}
}
ชั้นเรียนหลัก
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Activity a = new Activity();
OtherClass o = new OtherClass();
a.doSomeWork(a.mButton);
a.doSomeWork(a.mCheckBox);
}
}
ดังที่คุณเห็นจากโค้ดด้านบนเรามีอินเทอร์เฟซที่เรียกว่าเหตุการณ์ซึ่งโดยทั่วไปจะแสดงรายการเหตุการณ์ทั้งหมดที่อาจเกิดขึ้นสำหรับแอปพลิเคชันของเรา คลาส Widget เป็นคลาสพื้นฐานสำหรับส่วนประกอบ UI ทั้งหมดเช่นปุ่ม, ช่องทำเครื่องหมาย องค์ประกอบ UI เหล่านี้เป็นวัตถุที่รับเหตุการณ์จากรหัสเฟรมเวิร์ก คลาสวิดเจ็ตใช้อินเทอร์เฟซเหตุการณ์และมีอินเทอร์เฟซซ้อนกันสองรายการ ได้แก่ OnClickEventListener & OnLongClickEventListener
อินเทอร์เฟซสองเหล่านี้มีหน้าที่รับผิดชอบในการรับฟังเหตุการณ์ที่อาจเกิดขึ้นกับส่วนประกอบ UI ที่ได้รับจาก Widget เช่นปุ่มหรือช่องทำเครื่องหมาย ดังนั้นหากเราเปรียบเทียบตัวอย่างนี้กับตัวอย่างการติดต่อกลับก่อนหน้านี้โดยใช้ Java Interface ทั้งสองอินเตอร์เฟสนี้จะทำงานเป็นอินเตอร์เฟสการติดต่อกลับ ดังนั้นรหัสระดับที่สูงขึ้น (กิจกรรมที่นี่) จะใช้อินเทอร์เฟซทั้งสองนี้ และเมื่อใดก็ตามที่มีเหตุการณ์เกิดขึ้นกับวิดเจ็ตรหัสระดับที่สูงกว่า (หรือวิธีการของอินเทอร์เฟซเหล่านี้ถูกนำไปใช้ในรหัสระดับที่สูงขึ้น
ตอนนี้ให้ฉันพูดถึงความแตกต่างพื้นฐานระหว่างรูปแบบการโทรกลับและ Eventlistener ดังที่เราได้กล่าวไปแล้วว่าการใช้การโทรกลับผู้ใช้สามารถแจ้งเตือนได้เพียงผู้โทรเดียว แต่ในกรณีของรูปแบบ EventListener ส่วนอื่น ๆ หรือคลาสของแอปพลิเคชันสามารถลงทะเบียนสำหรับเหตุการณ์ที่อาจเกิดขึ้นในปุ่มหรือช่องทำเครื่องหมาย ตัวอย่างของคลาสประเภทนี้คือ OtherClass หากคุณเห็นรหัสของ OtherClass คุณจะพบว่ามันได้ลงทะเบียนตัวเองเป็นผู้ฟังของ ClickEvent ที่อาจเกิดขึ้นในปุ่มที่กำหนดไว้ในกิจกรรม ส่วนที่น่าสนใจคือนอกเหนือจากกิจกรรม (ตัวเรียก), OtherClass นี้จะได้รับแจ้งเมื่อใดก็ตามที่มีเหตุการณ์การคลิกเกิดขึ้นบนปุ่ม
การเรียกกลับช่วยให้คุณสามารถแทรกรหัสของคุณเองลงในบล็อกของรหัสอื่นที่จะดำเนินการในเวลาอื่นซึ่งจะปรับเปลี่ยนหรือเพิ่มลักษณะการทำงานของบล็อกรหัสอื่นที่เหมาะสมกับความต้องการของคุณ คุณได้รับความยืดหยุ่นและปรับแต่งได้ในขณะที่สามารถมีรหัสที่สามารถบำรุงรักษาได้มากขึ้น
hardcode น้อยลง = ง่ายต่อการดูแลและเปลี่ยนแปลง = เวลาน้อยลง = มูลค่าทางธุรกิจมากขึ้น = ยอดเยี่ยม
ตัวอย่างเช่นในจาวาสคริปต์โดยใช้ Underscore.js คุณสามารถค้นหาองค์ประกอบทั้งหมดได้ในอาร์เรย์ดังนี้
var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [2, 4, 6]
ตัวอย่างความอนุเคราะห์จาก Underscore.js: http://documentcloud.github.com/underscore/#filter
[แก้ไข] เมื่อเรามีสองฟังก์ชั่นบอกว่าfunctionA และ functionBถ้าfunctionAขึ้นอยู่กับfunctionB
ดังนั้นเราจึงเรียกใช้ functionBเป็นฟังก์ชันการเรียกกลับซึ่งใช้กันอย่างแพร่หลายในกรอบงาน Spring
ลองนึกถึงวิธีการหนึ่งในการมอบหมายงานให้ผู้ร่วมงาน ภารกิจง่าย ๆ อาจเป็นดังต่อไปนี้:
Solve these equations:
x + 2 = y
2 * x = 3 * y
เพื่อนร่วมงานของคุณขยันทำคณิตศาสตร์และให้ผลลัพธ์ต่อไปนี้:
x = -6
y = -4
แต่เพื่อนร่วมงานของคุณมีปัญหาเขาไม่เข้าใจสัญลักษณ์เช่น^
เสมอ แต่เขาเข้าใจคำอธิบายของพวกเขา exponent
เช่น ทุกครั้งที่เขาพบหนึ่งในสิ่งเหล่านี้คุณจะได้รับสิ่งต่อไปนี้:
I don't understand "^"
สิ่งนี้ต้องการให้คุณเขียนคำแนะนำทั้งหมดของคุณอีกครั้งหลังจากอธิบายว่าตัวละครนั้นมีความหมายอย่างไรต่อผู้ร่วมงานของคุณและเขาก็จำไม่ได้ว่าเกิดอะไรขึ้นระหว่างคำถาม และเขาก็มีปัญหาในการจำเคล็ดลับของคุณเช่นถามฉัน เขามักจะทำตามคำแนะนำของคุณเท่าที่จะทำได้
คุณคิดถึงวิธีแก้ปัญหาคุณเพียงเพิ่มคำแนะนำต่อไปนี้ลงในคำแนะนำทั้งหมด:
If you have any questions about symbols, call me at extension 1234 and I will tell you its name.
ตอนนี้เมื่อใดก็ตามที่เขามีปัญหาเขาโทรหาคุณและถามแทนที่จะให้การตอบสนองที่ไม่ดีและทำให้กระบวนการรีสตาร์ท
สิ่งนี้ในแง่ของการดาวน์โหลดเว็บเพจ:
โปรแกรมของคุณทำงานบนโทรศัพท์มือถือและมีการร้องขอหน้าเว็บhttp://www.google.com หากคุณเขียนโปรแกรมของคุณพร้อมกันฟังก์ชันที่คุณเขียนเพื่อดาวน์โหลดข้อมูลจะทำงานอย่างต่อเนื่องจนกว่าข้อมูลทั้งหมดจะถูกดาวน์โหลด ซึ่งหมายความว่า UI ของคุณจะไม่รีเฟรชและโดยทั่วไปจะปรากฏเป็นน้ำแข็ง หากคุณเขียนโปรแกรมของคุณโดยใช้การเรียกกลับคุณขอข้อมูลและพูดว่า "ดำเนินการฟังก์ชั่นนี้เมื่อคุณทำเสร็จแล้ว" สิ่งนี้ทำให้ UI ยังคงอนุญาตให้ผู้ใช้โต้ตอบในขณะที่ไฟล์กำลังดาวน์โหลด เมื่อเว็บเพจดาวน์โหลดเสร็จแล้วฟังก์ชันผลลัพธ์ (การเรียกกลับ) ของคุณจะถูกเรียกใช้และคุณสามารถจัดการข้อมูลได้
โดยทั่วไปจะช่วยให้คุณสามารถร้องขอบางสิ่งและดำเนินการต่อในขณะที่รอผล เมื่อผลลัพธ์กลับมาถึงคุณผ่านฟังก์ชั่นการโทรกลับคุณสามารถรับการดำเนินการที่ค้างไว้ได้