Сталкивались ли вы с термином «обратные вызовы», но не знаете, что он означает? Не волнуйтесь. ты не один. Многим новичкам в JavaScript также трудно понять обратные вызовы.
несмотря на то чтоcallbacksЭто может сбивать с толку, но вам все равно нужно досконально их понимать, потому что они являются одним изважные концепции. если ты не знаешьcallbacks, далеко не уедешь 🙁.
Вот и все для сегодняшней статьи (чтобы поговорить)! ты поймешьcallbacksчто это такое, почему они важны и как их использовать. 😄
Примечание. В этом посте вы увидите стрелочные функции ES6. Если вы не очень хорошо с ними знакомы, я предлагаю вам ознакомиться с ними, прежде чем читать дальше.ES6 эта статья(Просто знать, что часть функции стрелки в порядке).
Что такое обратные вызовы?
callbackэто функция, которая передается другой функции в качестве аргумента для последующего выполнения. (Разработчики говорят, что вы «вызываете» функцию, когда функция выполняется, поэтому она называется функцией обратного вызова).
Они настолько распространены в JavaScript, что вы можете использовать их подсознательно, не зная, что они называются функциями обратного вызова.
Пример приема обратного вызова функции:addEventLisnter:
const button = document.querySelector('button')
button.addEventListener('click', function(e) {
// Adds clicked class to button
this.classList.add('clicked')
})
Не видите функцию обратного вызова? Итак, как работает это правописание?
const button = document.querySelector('button')
// Function that adds 'clicked' class to the element
function clicked (e) {
this.classList.add('clicked')
}
// Adds click function as a callback to the event listener
button.addEventListener('click', clicked)
Здесь мы указываем JavaScript слушатьclickмероприятие. Если обнаружен щелчок, JavaScript должен сработатьclickedфункция. Итак, в этом случаеclickedэто функция обратного вызова, иaddEventListenerэто функция, которая принимает обратный вызов.
Теперь вы понимаете, что такое функция обратного вызова? :)
Давайте посмотрим на другой пример. На этот раз предположим, что вы хотите получить список менее 5, отфильтровав набор данных. Здесь вы передаете функцию обратного вызоваfilterфункция:
const numbers = [3, 4, 10, 20]
const lesserThanFive = numbers.filter(num => num < 5)
Теперь, если вы хотите выполнить приведенный выше код с помощью именованной функции, функция фильтра будет выглядеть так:
const numbers = [3, 4, 10, 20]
const getLessThanFive = num => num < 5
// Passing getLessThanFive function into filter
const lesserThanFive = numbers.filter(getLessThanFive)
при этих обстоятельствах,getLessThanFiveЭто функция обратного вызова.Array.filterэто функция, которая принимает обратный вызов.
Теперь понятно почему? Как только вы узнаете, что такое функции обратного вызова, они повсюду!
В следующем примере показано, как писать функции обратного вызова и функции, принимающие обратные вызовы:
// Create a function that accepts another function as an argument
const callbackAcceptingFunction = (fn) => {
// Calls the function with any required arguments
return fn(1, 2, 3)
}
// Callback gets arguments from the above call
const callback = (arg1, arg2, arg3) => {
return arg1 + arg2 + arg3
}
// Passing a callback into a callback accepting function
const result = callbackAcceptingFunction(callback)
console.log(result) // 6
Обратите внимание, что когда вы передаете функцию обратного вызова другой функции, вы только передаете ссылку на эту функцию (и не выполняете ее, поэтому скобки не используются).())
const result = callbackAcceptingFunction(callback)
ты можешь толькоcallbackAcceptingFunctionРазбудите (вызовите) функцию обратного вызова в . При этом вы можете передать столько аргументов, сколько может потребоваться функции обратного вызова:
const callbackAcceptingFunction = (fn) => {
// Calls the callback with three args
fn(1, 2, 3)
}
это поcallbackAcceptingFunctionАргументы, передаваемые функции обратного вызова, которые затем передаются через функцию обратного вызова (выполняются):
// Callback gets arguments from callbackAcceptingFunction
const callback = (arg1, arg2, arg3) => {
return arg1 + arg2 + arg3
}
Это анатомия обратных вызовов. Теперь вы должны знатьaddEventListenerсодержитeventпараметр :)
// Now you know where this event object comes from! :)
button.addEventListener('click', (event) => {
event.preventDefault()
})
Эй! ЭтоcallbacksОсновная идея! Просто запомните ключ:передать одну функцию другой, и тогда вы вспомните механизм, о котором я упоминал выше.
Примечание: эта способность передавать функции имеет большое значение. Это настолько важно, что говорят, что функции в JavaScript — это функции более высокого порядка. Функции высшего порядка, известные в парадигме программирования как функциональное программирование, имеют большое значение.
Но это тема для другого дня. Теперь, я уверен, вы начинаете пониматьcallbacksчто это такое и как они используются. но почему? зачем тебе нужноcallbacksШерстяная ткань?
Зачем использовать обратные вызовы
Функции обратного вызова используются двумя разными способами — в синхронных и асинхронных функциях.
Обратные вызовы в синхронных функциях
Если ваш код выполняется последовательно сверху вниз и слева направо, ожидая выполнения предыдущего кода перед выполнением следующей строки кода, ваш кодСинхронный.
Давайте рассмотрим пример, чтобы было легче понять:
const addOne = (n) => n + 1
addOne(1) // 2
addOne(2) // 3
addOne(3) // 4
addOne(4) // 5
В приведенном выше примереaddOne(1)Выполнить первым. Как только он завершит выполнение,addOne(2)Начать выполнение. однаждыaddOne(2)законченный,addOne(3)воплощать в жизнь. Этот процесс продолжается до тех пор, пока не будет выполнена последняя строка кода.
Обратные вызовы используются, когда вы хотите легко поменять местами части своего кода с другим кодом.Синхронизироватьфункция.
Итак, вернемся к вышеперечисленномуArray.filterВ этом примере, несмотря на то, что мы фильтруем массив, чтобы он содержал меньше 5, вы можете легко использовать его повторно.Array.filterчтобы получить массив чисел больше 10:
const numbers = [3, 4, 10, 20]
const getLessThanFive = num => num < 5
const getMoreThanTen = num => num > 10
// Passing getLessThanFive function into filter
const lesserThanFive = numbers.filter(getLessThanFive)
// Passing getMoreThanTen function into filter
const moreThanTen = numbers.filter(getMoreThanTen)
Вот почему вы используете функции обратного вызова в синхронных функциях. Теперь давайте продолжим и посмотрим, почему мы используем обратные вызовы в асинхронных функциях.
Обратные вызовы в асинхронных функциях
здесьасинхронныйЭто означает, что если JavaScript должен дождаться завершения чего-то, он выполнит остальные задачи, данные ему, пока ждет.
Пример асинхронной функции:setTimeout. Он принимает функцию обратного вызова для выполнения позже:
// Calls the callback after 1 second
setTimeout(callback, 1000)
Если вы дадите JavaScript выполнить другую задачу, давайте посмотримsetTimeoutКак это работает:
const tenSecondsLater = _ = > console.log('10 seconds passed!')
setTimeout(tenSecondsLater, 10000)
console.log('Start!')
В приведенном выше коде JavaScript будет выполнятьсяsetTimeout. Затем он ждет 10 секунд, а затем печатает сообщение «10 секунд прошло!».
Тем временем в ожиданииsetTimeoutКогда это делается в течение 10 секунд, JavaScript выполняетсяconsole.log("Start!").
Итак, если вы напечатаете приведенный выше код (в консоли), вы увидите следующее:
// What happens:
// > Start! (almost immediately)
// > 10 seconds passed! (after ten seconds)
А~ асинхронные операции кажутся сложными, не так ли? Но почему мы так часто используем его в JavaScript?
Чтобы понять, почему важны асинхронные операции? Представьте JavaScript в качестве робота-помощника в вашем доме. Этот помощник очень глуп. Он может делать только одну вещь за раз. (Это поведение называется однопоточной обработкой).
Предположим, вы просите своего робота-помощника заказать для вас пиццу. Но робот настолько глуп, что, позвонив в пиццерию, робот сидит перед вашим домом и ждет, когда пиццу доставят. В это время он не может делать ничего другого.
Вы не можете сказать ему гладить, мыть шваброй или делать что-либо, пока вы ждете (приносят пиццу). (вероятно) вам нужно подождать 20 минут, пока не принесут пиццу, и она больше ничего делать не захочет...
Такое поведение называется блокировкой. Пока вы ждете завершения чего-либо, другие операции будут заблокированы.
const orderPizza = flavour => {
callPizzaShop(`I want a ${flavour} pizza`)
waits20minsForPizzaToCome() // Nothing else can happen here
bringPizzaToYou()
}
orderPizza('Hawaiian')
// These two only starts after orderPizza is completed
mopFloor()
ironClothes()
А блокировать операции - это облом. 🙁
Почему?
Давайте поместим глупого робота-помощника в контекст браузера. Представьте, что когда кнопка нажата, вы говорите ей изменить цвет кнопки.
Что будет делать этот глупый робот?
Он фокусируется на кнопке и игнорирует все команды до тех пор, пока кнопка не будет нажата. При этом пользователь не может выбрать ничего другого. Видишь, что он делает? Вот почему асинхронное программирование так важно в JavaScript.
Однако, чтобы по-настоящему понять, что произошло во время асинхронной операции, нам нужно ввести еще один цикл вещь-событие.
цикл событий
Чтобы представить себе цикл событий, представьте, что JavaScript — этоtodo-listэкономка. Этот список содержит все, что вы ему скажете. Затем JavaScript будет перебирать список один за другим в том порядке, в котором вы его предоставляете.
Предположим, вы даете JavaScript следующие пять команд:
const addOne = (n) => n + 1
addOne(1) // 2
addOne(2) // 3
addOne(3) // 4
addOne(4) // 5
addOne(5) // 6
Это то, что появляется в списке дел JavaScript.
相关命令Синхронно появляется в списке дел JavaScript.
Кромеtodo-listКроме того, JavaScript также сохранилwaiting-listчтобы отслеживать, чего ему нужно ждать. Если вы скажете JavaScript заказать пиццу, он позвонит в пиццерию и добавит «ожидание доставки пиццы» (инструкцию) в список ожидания. В то же время он также будет делать другиеtodo-listвещи выше.
Итак, представьте, что у вас есть следующий код:
const orderPizza (flavor, callback) {
callPizzaShop(`I want a ${flavor} pizza`)
// Note: these three lines is pseudo code, not actual JavaScript
whenPizzaComesBack {
callback()
}
}
const layTheTable = _ => console.log('laying the table')
orderPizza('Hawaiian', layTheTable)
mopFloor()
ironClothes()
Инициализация JavaScripttodo-listследующим образом:
Закажи пиццу, швабру и утюг! 😄
Затем при выполненииorderPizza, JavaScript знает, что ему нужно дождаться доставки пиццы. Таким образом, он добавляет «ожидание доставки пиццы» (инструкцию) вwaiting listначальство.
JavaScript ждет доставки пиццы
Дверной звонок уведомит JavaScript, когда принесут пиццу и когда он выполнит остальную работу по дому. Это сделает **мысленную пометку** для выполненияlayTheTable.
JavaScript знает, что это нужно сделать, добавив команду в свою мысленную заметку.
layTheTable
Затем, как только другие дела будут выполнены, JavaScript выполнит функцию обратного вызова.layTheTable.
После того, как все остальное сделано, JavaScript переходит к layTheTable.
Друг мой, это называется циклом событий. Вы можете заменить нашу экономку фактическим ключевым словом в цикле событий, аналогия, чтобы все понять:
- Todo-list -> Call stack
- Waiting-list -> Web apis
- Mental note -> Event queue
Цикл событий JavaScript
Если у вас есть 20 минут свободного времени, настоятельно рекомендую посмотретьPhilip RobertsЦикл событий, о котором говорилось в JSconf. Это поможет вам понять детали цикла событий.
Э... Так почему же обратные вызовы так важны?
О ~ мы прошли весь цикл событий. Вернемся к делу 😂.
Ранее мы упоминали, что было бы плохо, если бы JavaScript фокусировался на кнопках и игнорировал все остальные команды. правильно?
С помощью асинхронных обратных вызовов мы можемПредоставляйте инструкции JavaScript заранее, не останавливая всю операцию.
Теперь, когда вы просите JavaScript увидеть нажатую кнопку, он поместит «кнопку прослушивания» (директиву) вwaiting listи продолжайте заниматься делами. Когда кнопка, наконец, нажата, JavaScript запускает обратный вызов и продолжает выполнение.
Вот несколько распространенных способов использования обратных вызовов, чтобы сообщить JavaScript, что делать...
- Когда происходит событие (например,
addEventListener) - после вызова AJAX (например,
jQuery.ajax) - После чтения/записи файла (например,
fs.readFile)
// Callbacks in event listeners
document.addEventListener(button, highlightTheButton)
document.removeEventListener(button, highlightTheButton)
// Callbacks in jQuery's ajax method
$.ajax('some-url', {
success (data) { /* success callback */ },
error (err) { /* error callback */}
});
// Callbacks in Node
fs.readFile('pathToDirectory', (err, data) => {
if (err) throw err
console.log(data)
})
// Callbacks in ExpressJS
app.get('/', (req, res) => res.sendFile(index.html))
Вот и все (асинхронный) обратный вызов! 😄
надеюсь, вы ясноcallbacksчто такое и как их использовать сейчас. Вначале вы не будете создавать много обратных вызовов, поэтому сосредоточьтесь на изучении того, как использовать доступные функции обратного вызова.
Теперь, прежде чем мы завершим (эту статью), давайте рассмотрим первую проблему для разработчиков (использующих) обратные вызовы - ад обратных вызовов.
ад обратного звонка
ад обратного звонкаЭто явление, при котором несколько обратных вызовов вложены друг в друга. Это может произойти, когда вы выполняете асинхронное действие, которое зависит от предыдущего асинхронного действия. Эти вложенные обратные вызовы затрудняют чтение кода.
По моему опыту, вы толькоNodeсм. ад обратного вызова. используя переднюю частьJavaScript, вы почти никогда не сталкиваетесь с адом обратных вызовов.
Вот пример ада обратного вызова:
// Look at three layers of callback in this code!
app.get('/', function (req, res) {
Users.findOne({ _id:req.body.id }, function (err, user) {
if (user) {
user.update({/* params to update */}, function (err, document) {
res.json({user: document})
})
} else {
user.create(req.body, function(err, document) {
res.json({user: document})
})
}
})
})
А теперь у вас есть задача — попробуйте расшифровать приведенный выше код с первого взгляда. Это тяжело, не так ли? Неудивительно, что разработчики вздрагивают, когда видят вложенные обратные вызовы.
Одним из способов преодоления ада обратного вызова является разбиение функции обратного вызова на более мелкие части, чтобы уменьшить объем вложенного кода:
const updateUser = (req, res) => {
user.update({/* params to update */}, function () {
if (err) throw err;
return res.json(user)
})
}
const createUser = (req, res, err, user) => {
user.create(req.body, function(err, user) {
res.json(user)
})
}
app.get('/', function (req, res) {
Users.findOne({ _id:req.body.id }, (err, user) => {
if (err) throw err
if (user) {
updateUser(req, res)
} else {
createUser(req, res)
}
})
})
Легче читать, да?
Есть и другие решения для борьбы с адом обратных вызовов в новых версиях JavaScript, напримерpromisesа такжеasync / await. Однако их объяснение — наша тема для другого дня.
Эпилог
Сегодня вы узнали, что такое обратные вызовы, почему они так важны в JavaScript и как их использовать. Вы также узнали об аде обратного вызова и способах борьбы с ним. Теперь, надеюсьcallbakcsНе пугай тебя больше 😉.
У вас есть вопросы по обратному звонку? Если у вас есть, пожалуйста, не стесняйтесь оставлять комментарии ниже, я отвечу вам как можно скорее. [PS: Перевод этой статьи, если вам нужно ответить на вопросы, пожалуйста, переместитеОригинальная авторская статьякомментарий ниже]
Спасибо за прочтение. Вам помогла эта статья? Если есть, я надеюсь, вы подумаете о том, чтобы поделиться им. Возможно, вы сможете помочь другим. большое спасибо!
позже
Статья впервые опубликована:GitHub.com/still99/блог…
Следующая статья об обещаниях
больше контента:GitHub.com/still99/блог…
by the way, Happy International Workers' Day!