Як пояснити зворотні дзвінки простою англійською? Чим вони відрізняються від виклику однієї функції від іншої функції, приймаючи певний контекст від функції виклику? Як можна пояснити їх владу початківцю програмісту?
Як пояснити зворотні дзвінки простою англійською? Чим вони відрізняються від виклику однієї функції від іншої функції, приймаючи певний контекст від функції виклику? Як можна пояснити їх владу початківцю програмісту?
Відповіді:
Часто програмі потрібно виконувати різні функції залежно від її контексту / стану. Для цього ми використовуємо змінну, де ми б зберігали інформацію про функцію, яку потрібно викликати. Відповідно до своєї потреби, додаток встановить цю змінну з інформацією про функцію, яку потрібно викликати, і буде викликати функцію за допомогою тієї ж змінної.
У 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)');
}
Ось приклад зворотного виклику за допомогою getJSON jQuery :
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
з функції виклику за допомогою кнопки a 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
від Дугласа Crockford :
Функції можна визначити всередині інших функцій. Внутрішня функція має доступ до параметрів і параметрів зовнішньої функції. Якщо посилання на внутрішню функцію зберігається (наприклад, як функція зворотного виклику), зміни зовнішньої функції також зберігаються.
Дивись також:
Мені приголомшено бачити стільки розумних людей, які не в змозі підкреслити реальність, що слово "зворотний виклик" стало вживатися двома непослідовними способами.
Обидва способи включають в себе налаштування функції шляхом передачі додаткової функціональності (визначення функції, анонімного або названого) до існуючої функції. тобто.
customizableFunc(customFunctionality)
Якщо спеціальна функціональність просто підключена до блоку коду, ви налаштували функцію, як-от так.
customizableFucn(customFunctionality) {
var data = doSomthing();
customFunctionality(data);
...
}
Хоча цей вид введеної функціональності часто називають "зворотним дзвоном", в цьому немає нічого умовного. Дуже очевидним прикладом є метод forEach, в якому спеціальна функція подається як аргумент, який слід застосувати до кожного елемента масиву для зміни масиву.
Але це принципово відрізняється від використання функцій "зворотного виклику" для асинхронного програмування , як в AJAX або node.js, або просто у призначенні функціональності подіям взаємодії з користувачем (наприклад, клацанням миші). У цьому випадку вся ідея полягає в тому, щоб дочекатися виникнення непередбачуваної події перед виконанням спеціальної функції. Це очевидно у випадку взаємодії з користувачем, але також важливо в процесах i / o (введення / виведення), які можуть зайняти час, як читання файлів з диска. Тут термін «зворотний виклик» має найбільш очевидний сенс. Після запуску процесу вводу / виводу (наприклад, прохання прочитати файл з диска або сервера для повернення даних із запиту http) асинхроннийпрограма не чекає, поки вона закінчиться. Він може продовжувати виконання завдань, запланованих далі, і відповідати лише за допомогою спеціальної функціональності після повідомлення про те, що файл читання або запит http виконаний (або що він не вдався) та що дані доступні для користувацької функціональності. Це як зателефонувати компанії по телефону та залишити номер "зворотного дзвінка", щоб вони могли зателефонувати вам, коли хтось буде доступний, щоб повернутися до вас. Це краще, ніж висіти на черзі для того, хто знає, як довго і не мати можливості відвідувати інші справи.
Асинхронне використання по суті включає деякі засоби прослуховування бажаної події (наприклад, завершення процесу вводу / виводу), щоб при його виникненні (і лише тоді, коли він відбувається) користувальницька функція "зворотного виклику" виконувалася. У очевидному прикладі AJAX, коли дані фактично надходять із сервера, функція "зворотного виклику" запускається для використання цих даних для зміни DOM і, отже, перемальовує вікно браузера до такої міри.
Для резюме. Деякі люди використовують слово "зворотний виклик" для позначення будь-якого виду користувацьких функцій, які можуть бути введені в існуючу функцію як аргумент. Але, принаймні, для мене, найдоцільніше використовувати слово - це те, де введена функція "зворотного виклику" використовується асинхронно - виконується лише в разі настання події, про яку вона чекає повідомлення.
Array.prototype.forEach()
і функцією, переданою як аргумент setTimeout()
, і вони є конями різного кольору, наскільки ви міркуєте про свою програму .
У непрограмістському відношенні зворотний виклик - це заповнення порожнього в програмі.
Поширений пункт на багатьох паперових бланках - «Особа, яку потрібно викликати у разі надзвичайної ситуації». Там є порожній рядок. Ви пишете на чиєсь ім’я та номер телефону. Якщо виникла надзвичайна ситуація, то людині телефонують.
Це ключове. Ви не змінюєте форму (код, як правило, чужий). Однак ви можете заповнити відсутні дані ( ваш номер).
Приклад 1:
Відклики викликів використовуються як спеціалізовані методи, можливо для додавання / зміни поведінки програми. Наприклад, візьміть якийсь код C, який виконує функцію, але не знає, як друкувати вихід. Все, що він може зробити - це зробити рядок. Коли він намагається з'ясувати, що робити з рядком, він бачить порожній рядок. Але програміст дав вам бланк для зворотного дзвінка!
У цьому прикладі ви не використовуєте олівець для заповнення бланка на аркуші паперу, ви використовуєте функцію set_print_callback(the_callback)
.
set_print_callback
це олівець,the_callback
це ваша інформація, яку ви заповнюєте.Тепер ви заповнили цей порожній рядок у програмі. Щоразу, коли йому потрібно надрукувати вихід, він перегляне цей порожній рядок і дотримуватиметься там інструкцій (тобто зателефонуйте до функції, яку ви туди вклали.) Практично це дозволяє можливість друку на екрані, у файлі журналу, на принтері, через мережеве з'єднання або будь-яку їх комбінацію. Ви заповнили бланк тим, що хочете зробити.
Приклад 2:
Коли вам скажуть, що вам потрібно зателефонувати на номер екстреної допомоги, ви переходите і читаєте, що написано на паперовій формі, а потім телефонуєте за прочитаним номером. Якщо цей рядок порожній, нічого не буде зроблено.
Програмування Gui працює так само. Після натискання кнопки програма повинна визначити, що робити далі. Він іде і шукає зворотний дзвінок. Цей зворотний виклик міститься в порожній написі "Ось що ви робите, коли натискаєте кнопку Button1"
Більшість IDE автоматично заповнюють бланк для вас (напишіть основний метод), коли ви попросите його (наприклад button1_clicked
). Однак цей бланк може мати будь-який метод, який ви проклинаєте, будь ласка . Ви можете зателефонувати за методом run_computations
або butter_the_biscuits
доки введете ім'я цього зворотного дзвінка у відповідне поле. Ви можете поставити "555-555-1212" у номер екстреної допомоги. Це не має великого сенсу, але допустимо.
Заключна примітка: той порожній рядок, який ви заповнюєте зворотним дзвінком? Його можна стерти і переписати за бажанням. (варто чи ні - це інше питання, але це частина їхньої сили)
Завжди краще почати з прикладу :).
Припустимо, у вас є два модулі A і B.
Ви хочете, щоб модуль A отримував сповіщення, коли в модулі B. виникає якась подія / умова. Однак модуль B поняття не має про ваш модуль А. Все, що він знає, - це адреса до певної функції (модуля A) через покажчик функції, який є наданий йому модулем А.
Отже, все, що B має зробити зараз, це "зворотний виклик" в модуль A, коли конкретна подія / стан відбувається за допомогою функції вказівника. 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) і обчислити (doB).
Крім спрощеного коду, він дозволяє асинхронному коду повідомляти, що він завершився, зателефонувавши довільної функції після завершення, подібно до того, коли ви телефонуєте комусь по телефону та залишаєте номер зворотного дзвінка.
Джоні програмісту потрібен степлер, тому він спускається до відділу постачання офісу і запитує його, після заповнення форми запиту він може або стояти там, і чекати, коли клерк перегляне склад для степлера (як виклик функції блокування ) або піти робити тим часом щось інше.
Оскільки для цього зазвичай потрібен час, Джоні кладе записку разом із формою запиту, просячи зателефонувати йому, коли степлер готовий до пікапу, тож тим часом він може піти робити щось інше, як дрімоту на своєму столі.
Вам погано, тому ви йдете до лікаря. Він обстежує вас і визначає, що вам потрібно певні ліки. Він виписує деякі ліки і закликає рецепт у вашу місцеву аптеку. Ви йдете додому. Пізніше ваша аптека дзвонить, щоб повідомити, що ваш рецепт готовий. Ти йдеш і береш.
Можна пояснити два моменти: один - як працює зворотний виклик (проходить навколо функції, яку можна викликати, не знаючи її контексту), інший - для чого вона використовується (асинхронно обробляє події).
Аналогія очікування надходження посилки, яка була використана іншими відповідями, є хорошою для пояснення обох. У комп'ютерній програмі ви б сказали комп'ютеру очікувати посилки. Зазвичай, він би зараз сидів там і чекав (і більше нічого не робив), поки посилка приїде, можливо, безстроково, якщо вона ніколи не приїде. Для людей це звучить нерозумно, але без подальших заходів це абсолютно природно для комп'ютера.
Тепер зворотний дзвінок буде дзвоном у ваші вхідні двері. Ви надаєте послугу посилки способом сповіщати вас про приїзд посилки, не знаючи, де ви перебуваєте в будинку або як працює дзвін. (Наприклад, деякі "дзвіночки" насправді відправляють телефонний дзвінок.) Оскільки ви надали "функцію зворотного дзвінка", яку можна "викликати" в будь-який час, поза контекстом, ви тепер можете перестати сидіти на передній ганку та "обробити подія "(про прибуття посилки) кожного разу, коли настав час.
Уявіть, що друг виходить з вашого будинку, і ви говорите їй "Подзвони мені, коли ти повернешся додому, щоб я знав, що ти приїхав благополучно"; тобто (буквально) зворотній дзвінок . Ось що таке функція зворотного дзвінка незалежно від мови. Ви хочете, щоб якась процедура передала вам управління, коли воно виконало якесь завдання, тому ви надаєте йому функцію, яку можна використовувати для того, щоб передзвонити вам.
У Python, наприклад,
grabDBValue( (lambda x: passValueToGUIWindow(x) ))
grabDBValue
можна записати лише для того, щоб схопити значення з бази даних, а потім дозволити вам вказати, що насправді робити зі значенням, тому воно приймає функцію. Ви не знаєте, коли чи grabDBValue
повернеться, але якщо / коли це станеться, ви знаєте, що ви хочете це зробити. Тут я передаю анонімну функцію (або лямбда ), яка надсилає значення у вікно GUI. Я міг легко змінити поведінку програми, зробивши це:
grabDBValue( (lambda x: passToLogger(x) ))
Зворотні дзвінки добре працюють в мовах, де функції - це значення першого класу , як і звичайні цілі числа, рядки символів, булеві і т.д. у Java виклик запитає статичний клас певного типу з певною назвою методу, оскільки поза класами немає функцій ("методів"); а в більшості інших динамічних мов ви можете просто передати функцію простим синтаксисом.
У мовах з лексичним визначенням (наприклад, схема або 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);
Це, мабуть, можна вдосконалити багатьма способами. Наприклад, ви можете надати другий зворотний дзвінок: якщо це закінчиться більше години, зателефонуйте на червоний телефон і повідомте людині, яка відповідає, що ви вичерпали час.
Зворотні виклики найлегше описати з точки зору телефонної системи. Виклик функції аналогічний дзвінку комусь по телефону, задає їй запитання, отримає відповідь і повісить трубку; додавання зворотного дзвінка змінює аналогію, так що, задавши їй питання, ви також дасте їй своє ім’я та номер, щоб вона могла передзвонити вам з відповіддю. - Пол Якубік, "Реалізація зворотних дзвінків у C ++"
Зворотний виклик - це функція, яку викличе друга функція. Ця друга функція не знає заздалегідь, яку функцію вона буде викликати. Тож ідентичність функції зворотного виклику зберігається десь або передається другій функції як параметр. Ця "ідентичність", залежно від мови програмування, може бути адресою зворотного дзвінка або якимсь іншим вказівником, або це може бути назва функції. Головне - те саме, ми зберігаємо або передаємо певну інформацію, яка однозначно ідентифікує функцію.
Коли настає час, друга функція може викликати зворотний дзвінок, надаючи параметри залежно від обставин на той момент. Він може навіть вибрати зворотний виклик з набору можливих зворотних дзвінків. Мова програмування повинен містити якийсь синтаксис, щоб дозволити другій функції викликати зворотний виклик, знаючи його "ідентичність".
Цей механізм має дуже багато можливих застосувань. За допомогою зворотних дзвінків, дизайнер функції може дозволити її налаштовувати, викликаючи її виклик, будь-який режим зворотного виклику надається. Наприклад, функція сортування може приймати зворотний виклик як параметр, і цей зворотний виклик може бути функцією для порівняння двох елементів, щоб вирішити, який з них стає першим.
До речі, залежно від мови програмування слово "функція" у вищезгаданій дискусії може бути замінено на "блок", "закриття", "лямбда" тощо.
Зазвичай ми надсилаємо змінні функції. Припустимо, у вас є завдання, де змінна повинна бути оброблена, перш ніж її подавати в якості аргументу - ви можете використовувати зворотний дзвінок.
function1(var1, var2)
це звичайний спосіб.
Що робити, якщо я хочу var2
обробити та надсилати як аргумент?
function1(var1, function2(var2))
Це один тип зворотного виклику - де function2
виконується деякий код і повертається змінна назад до початкової функції.
Метафоричне пояснення:
У мене є посилка, яку я хочу доставити другові, і я також хочу знати, коли мій друг її отримає.
Тож я беру посилку на пошту і прошу їх доставити. Якщо я хочу знати, коли мій друг отримає посилку, у мене є два варіанти:
(а) Я можу зачекати у поштовому відділенні, поки його не доставлять.
(b) Я отримаю електронний лист, коли він буде доставлений.
Варіант (b) є аналогом зворотного дзвінка.
Для навчання зворотних викликів вам потрібно спочатку навчити вказівник. Як тільки учні зрозуміють ідею вказівника на змінну, ідея зворотного виклику стане простішою. Якщо припустити, що ви використовуєте C / C ++, ці кроки можна виконати.
Тут може бути багато іншого. Залучіть учнів, і вони відкриють для себе. Сподіваюсь, це допомагає.
Простий англійською мовою зворотний дзвінок - обіцянка. Джо, Джейн, Девід і Саманта діляться автомобільним транспортом для роботи. Джо веде сьогодні. У Джейн, Девіда та Саманти є кілька варіантів:
Варіант 1: Це більше схожий на приклад опитування, коли Джейн застрягла б у "петлі", перевіряючи, чи знаходиться Джо поза. Джейн не може робити нічого іншого середнього часу.
Варіант 2: Це приклад зворотного дзвінка. Джейн каже Джо, щоб він задзвонив у двері, коли він зовні. Вона надає йому "функцію" дзвонити у дзвінок дверей. Джо не потрібно знати, як працює дзвінок дверей або де він знаходиться, йому просто потрібно зателефонувати на цю функцію, тобто задзвонити дзвінок у двері, коли він там.
Відклики викликів визначаються "подіями". У цьому прикладі "подія" - це прихід Джо. Наприклад, в Ajax події можуть мати "успіх" або "збій" асинхронного запиту, і кожен може мати однакові або різні зворотні виклики.
Що стосується програм JavaScript та зворотних дзвінків. Нам також потрібно зрозуміти "закриття" та контекст програми. Те, на що "це" відноситься, може легко заплутати розробників JavaScript. У цьому прикладі в методі / зворотному виклику кожної людини можуть бути деякі інші методи, які потрібно робити кожній людині, базуючись на ранковій програмі, наприклад. "turn_off_the_tv ()". Ми хотіли б, щоб "це" стосувалося об'єкта "Джейн" або об'єкта "Девід", щоб кожен міг налаштувати все, що потрібно, перш ніж Джо підбере їх. Тут налаштування зворотного дзвінка з Джо вимагає пародії методу так, що "це" відноситься до потрібного об'єкта.
Сподіваюся, що це допомагає!
Я думаю, що це досить легко пояснити.
Спочатку зворотний виклик - це лише звичайні функції.
А далі ми називаємо цю функцію (назвемо її A) всередині іншої функції (назвемо її B).
Магія цього полягає в тому, що я вирішую, яку функцію слід викликати функцією ззовні B.
У той час, коли я пишу функцію BI, я не знаю, яку функцію зворотного виклику слід викликати. У той час, коли я викликаю функцію BI, я також розказую цю функцію для виклику функції А. Це все.
Що таке функція зворотного дзвінка?
Проста відповідь на це перше питання полягає в тому, що функція зворотного дзвінка - це функція, яка викликається через покажчик функції. Якщо ви передаєте вказівник (адресу) функції як аргумент іншій, коли цей вказівник використовується для виклику функції, він вказує на нього, йдеться про те, що виконується зворотний дзвінок.
Функцію зворотного дзвінка важко відстежити, але іноді це дуже корисно. Особливо, коли ви проектуєте бібліотеки. Функція зворотного дзвінка - це як попросити свого користувача дати ім'я функції, і ви зателефонуєте їй за певної умови.
Наприклад, ви пишете таймер зворотного дзвінка. Це дозволяє вказати тривалість і яку функцію викликати, і функція буде відповідно зворотним дзвінкам. "Запускайте мою функцію () кожні 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. Замість того, щоб кожен день ходити в магазин, щоб побачити, чи він у вас, ви реєструєте свій електронний лист у списку, щоб отримувати повідомлення, коли гра буде доступна. Електронний лист стає вашим "зворотним дзвінком", а умовою, яку потрібно виконати, є доступність гри.
Приклад "програмістів" - це веб-сторінка, на якій потрібно виконати дію при натисканні кнопки. Ви зареєструєте метод зворотного дзвінка для кнопки та продовжуєте виконувати інші завдання. Коли / якщо користувач натискає кнопку, браузер перегляне список зворотних викликів для цієї події та зателефонує вашому методу.
Зворотний виклик - це спосіб асинхронно керувати подіями. Ви ніколи не можете знати, коли буде виконано зворотний виклик, чи він буде виконаний взагалі. Перевага полягає в тому, що він звільняє ваші програми та цикли процесора для виконання інших завдань під час очікування відповіді.
Простий і простий: Зворотний виклик - це функція, яку ви надаєте іншій функції, щоб вона могла викликати її.
Зазвичай його називають, коли деяка операція завершена. Оскільки ви створюєте зворотний дзвінок перед тим, як передати його іншій функції, ви можете ініціалізувати його з контекстною інформацією з сайту виклику. Ось чому його називають викликом * назад * - перша функція передзвонює в контекст, звідки вона була викликана.
«У комп’ютерному програмуванні зворотний виклик - це посилання на виконуваний код або фрагмент виконуваного коду, який передається як аргумент іншому коду. Це дозволяє програмному шару нижчого рівня викликати підпрограму (або функцію), визначену в шарі більш високого рівня. " - Вікіпедія
Зворотний виклик в C за допомогою функціонального вказівника
У C зворотний виклик реалізується за допомогою функціонального покажчика. Функція Покажчик - як випливає з назви, є вказівником на функцію.
Наприклад, 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 не існує концепції покажчика функції. Він реалізує механізм зворотного виклику через механізм інтерфейсу. Тут замість вказівника функції ми оголошуємо інтерфейс, що має метод, який буде викликаний, коли виклик закінчить своє завдання
Дозвольте продемонструвати це на прикладі:
Інтерфейс зворотного виклику
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 полягає в тому, що при зворотному виклику виклик сповіщає одного абонента, тоді як у Eventlisener / 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);
}
}
Як видно з наведеного вище коду, у нас є інтерфейс, який називається подіями, який в основному перераховує всі події, які можуть статися з нашим додатком. Клас віджетів є базовим класом для всіх компонентів інтерфейсу, таких як кнопка, прапорець. Ці компоненти інтерфейсу є об'єктами, які насправді отримують події з рамкового коду. Клас віджетів реалізує інтерфейс подій, а також має два вкладені інтерфейси, а саме OnClickEventListener та OnLongClickEventListener
Ці два інтерфейси відповідають за прослуховування подій, які можуть статися на компонентах інтерфейсу, похідних від віджетів, таких як кнопка або прапорець. Тож якщо ми порівняємо цей приклад із попереднім прикладом зворотного виклику за допомогою інтерфейсу Java, ці два інтерфейси працюють як інтерфейс зворотного виклику. Отже, код вищого рівня (Here Activity) реалізує ці два інтерфейси. І щоразу, коли подія трапляється на віджет, буде викликатися код вищого рівня (або метод цих інтерфейсів, реалізований у коді вищого рівня, який тут є Діяльність).
Тепер дозвольте мені обговорити основну різницю між шаблоном зворотного виклику та Eventlistener. Як ми вже згадували, що, використовуючи зворотний виклик, Callee може повідомити лише одного абонента. Але у випадку з шаблоном EventListener будь-яка інша частина або клас Додатку може зареєструвати події, які можуть статися на кнопці або прапорці. Прикладом такого роду класів є OtherClass. Якщо ви побачите код OtherClass, ви виявите, що він зареєструвався як слухач ClickEvent, який може виникнути в кнопці, визначеній у розділі Діяльність. Цікава частина полягає в тому, що, окрім діяльності (абонента), цей OtherClass також буде повідомлений, коли подія натискання відбувається на кнопці.
Відкликання викликів дозволяє вставити власний код в інший блок коду, який потрібно виконати в інший час, що змінює або доповнює поведінку цього іншого блоку коду відповідно до ваших потреб. Ви отримуєте гнучкість та налаштованість, маючи змогу мати більш корисний код.
Менше жорсткого коду = простіше в обслуговуванні та зміні = менше часу = більше ділової вартості = дивовижність.
Наприклад, у javascript, використовуючи 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
[відредаговано], коли у нас є дві функції, скажімо, функція A і functionB , якщо функція A залежить від functionB .
то ми називаємо functionB як функцію зворотного виклику. Це широко використовується у весняних рамках.
Подумайте про метод як надання завдання колезі. Простим завданням може бути наступне:
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 . Якщо ви пишете програму синхронно, функція, яку ви записуєте для завантаження даних, буде працювати постійно, поки всі дані не завантажуються. Це означає, що користувальницький інтерфейс не оновиться і в основному здасться замороженим. Якщо ви пишете програму за допомогою зворотних дзвінків, ви запитуєте дані та говорите "виконати цю функцію, коли закінчите". Це дозволяє користувальницькому інтерфейсу все ще дозволяти взаємодію з користувачем під час завантаження файлу. Після завершення завантаження веб-сторінки викликається функція результату (зворотний дзвінок), і ви можете обробляти дані.
В основному це дозволяє вам щось запитувати і продовжувати виконувати, чекаючи результату. Після того, як результат повернеться до вас за допомогою функції зворотного дзвінка, ви можете вибрати операцію там, де він припинився.