重慶分公司,新征程啟航
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊、服務(wù)器等服務(wù)
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊、服務(wù)器等服務(wù)
小編給大家分享一下javascript中的回調(diào)是什么,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討吧!
我們提供的服務(wù)有:成都網(wǎng)站設(shè)計、成都做網(wǎng)站、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認證、洪洞ssl等。為上千家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的洪洞網(wǎng)站制作公司
你有無意中看到 "callback" 但并不知道其中的意思么?不用擔心。不是只有你一個人這樣。很多JavaScript 新手都難以理解回調(diào)。
雖然回調(diào)比較令人困惑,你仍然需要徹底的學(xué)習理解它們,因為它在 JavaScript 中是一個很關(guān)鍵的概念。如果你不知道回調(diào),那么你無法走的長遠。
這篇文章中你會看到 ES6 里的箭頭函數(shù)。如果你還不熟悉它們,我建議你先看看ES6 post。(只要閱讀箭頭函數(shù)部分)。
回調(diào)是一個函數(shù),會作為一個參數(shù)傳遞到另一個函數(shù)中,并稍后去執(zhí)行。(開發(fā)人員說在執(zhí)行函數(shù)時調(diào)用另一個函數(shù),這就是為什么 callbacks
稱之為回調(diào)的原因)。
它們在 JavaScript 中很常見,以至于你可能不知道它們是回調(diào)函數(shù)的時候已經(jīng)使用過它們。
一個可以接收回調(diào)函數(shù)的例子是addEventLisnter
:
const button = document.querySelector('button') button.addEventListener('click', function(e) { // Adds clicked class to button this.classList.add('clicked') })
沒看出來這是個回調(diào)?來看看下個例子。
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 會執(zhí)行clicked
函數(shù)。所以,在這個例子中,當addEventListener
函數(shù)接收一個回調(diào)函數(shù)時,clicked
是一個回調(diào)。
現(xiàn)在知道回調(diào)是什么了么?:)
我們來看看另外一個例子。這一次,我們假設(shè)你想過濾一個數(shù)字數(shù)組來得到一個小于5
的列表。這里,你給filter
函數(shù)傳遞了一個回調(diào)函數(shù)。
const numbers = [3, 4, 10, 20] const lesserThanFive = numbers.filter(num => num < 5)
現(xiàn)在,如果你把上面的代碼用具名函數(shù)改一下,那么過濾數(shù)組就會變成這樣:
const numbers = [3, 4, 10, 20] const getLessThanFive = num => num < 5 // Passing getLessThanFive function into filter const lesserThanFive = numbers.filter(getLessThanFive)
在這個例子中,getLessThanFive
是個回調(diào)。Array.filter
是一個可以接收回調(diào)的函數(shù)。
現(xiàn)在看看?當你知道回調(diào)后會發(fā)現(xiàn)無處不在。
下面這個例子告訴你怎么寫一個回調(diào)函數(shù)和一個可以接收回調(diào)的函數(shù)。
// 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
請注意,當你把回調(diào)傳給另一個函數(shù)時,只是把引用傳遞過去了(不執(zhí)行,因此沒有()
)
`const result = callbackAcceptingFunction(callback)`
你只能在callbackAcceptingFunction
里調(diào)用這個回調(diào)當你這么做時,你可以給這個回調(diào)函數(shù)傳遞可能需要任意數(shù)量的參數(shù):
const callbackAcceptingFunction = (fn) => { // Calls the callback with three args fn(1, 2, 3) }
這些參數(shù)通過callbackAcceptingFunction
傳遞到回調(diào)里,然后用它們的方式在回調(diào)里進行傳遞:
// Callback gets arguments from callbackAcceptingFunction const callback = (arg1, arg2, arg3) => { return arg1 + arg2 + arg3 }
這是一個回調(diào)的結(jié)構(gòu)。現(xiàn)在,你知道了addEventListener
包含了event
參數(shù):
// Now you know where this event object comes from! :) button.addEventListener('click', (event) => { event.preventDefault() })
??!這是回調(diào)的基本含義!只要記住關(guān)鍵字:將一個函數(shù)傳遞到另一個函數(shù)中,你將回想起上面提到的機制。
這種傳遞函數(shù)的能力是一個很大的事情。它是如此之大,以至于 JavaScript 中的函數(shù)都是高階函數(shù)。高階函數(shù)是函數(shù)式編程范式中非常重要的東西。
但我們現(xiàn)在并不討論這個話題?,F(xiàn)在,我確信你已經(jīng)知道了回調(diào)以及如何使用了。但是,你為什么需要使用回調(diào)呢?
回調(diào)有二種不同的使用方式 - 在同步函數(shù)和在異步函數(shù)中。
同步函數(shù)中的回調(diào)
如果你的代碼執(zhí)行是一個從上到下,從做到右的方式,順序地,在下一行代碼執(zhí)行前會等到代碼執(zhí)行完成,那么你的代碼是同步的。
我們來看個例子,以便于更早的理解:
const addOne = (n) => n + 1 addOne(1) // 2 addOne(2) // 3 addOne(3) // 4 addOne(4) // 5
在上面的例子中,addOne(1)
先執(zhí)行。當執(zhí)行完成時,addOne(2)
開始執(zhí)行。當addOne(2)
執(zhí)行完成時,addOne(3)
開始執(zhí)行。這個過程一直執(zhí)行到最后一行代碼被執(zhí)行。
但你想讓一部分代碼跟其他交換簡單時,這時候可以在同步的函數(shù)里使用回調(diào)。
所以,回到上面的Array.filter
例子,雖然過濾數(shù)組讓其包含小于5
的數(shù)字,同樣地你也可以復(fù)用Array.filter
去包含大于10
的數(shù)字。
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)
這是你為什么在同步函數(shù)中使用回調(diào)?,F(xiàn)在,讓我們繼續(xù)看看為什么我們在異步函數(shù)里使用回調(diào)。
異步函數(shù)里的回調(diào)
這里異步的意思是,如果 JavaScript 需要等待某個東西完成,在等待的過程中會執(zhí)行其余的任務(wù)。
一個異步函數(shù)例子就是setTimeout
。它會一段時間后執(zhí)行回調(diào)函數(shù)。
// Calls the callback after 1 second setTimeout(callback, 1000)
如果你給JavaScript 另一個任務(wù)去完成時我們看看setTimeout
是怎么工作的:
const tenSecondsLater = _ = > console.log('10 seconds passed!') setTimeout(tenSecondsLater, 10000) console.log('Start!')
在上面的代碼里,JavaScript 去執(zhí)行setTimeout
。這時,會等待10
秒且打印日志“10 seconds passed!”。
同時,在等到10秒去執(zhí)行setTimeout
時,JavaScript 會執(zhí)行console.log("Start!")
。
因此,如果你記錄上面的代碼,你會看到這一點。
// What happens: // > Start! (almost immediately) // > 10 seconds passed! (after ten seconds)
啊。異步操作聽起來很復(fù)雜,不是么?但是我們?yōu)槭裁丛?JavaScript 里到處使用呢?
要理解為什么異步操作很重要,想象一下 JavaScript 是你家里的一個機器人助手。這個助手很蠢。一次只能做一件事情。(這個行為稱之為單線程)。
假設(shè)你告訴機器人助手幫你訂點披薩。但是機器人助手如此蠢,在給披薩店打完電話后,機器人助手坐在你家門前,慢慢的等待披薩送來。在這個過程中不能做任何其他的事情。
等待的過程中,你不能讓它去熨燙衣服,拖地板以及其他任何事情。你需要等20分鐘,直到披薩送來,才愿意做其他的事情。
這個行為稱之為阻塞。在等待一個任務(wù)執(zhí)行完全之前,其他的操作被阻止了。
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()
現(xiàn)在,阻塞操作是非常令人失望的。
為什么?
我們把愚蠢的機器人助手放在瀏覽器的運行環(huán)境里。想象一下,當按鈕被點擊時需要改變按鈕的顏色。
那這個愚蠢的機器人會怎么做呢?
它會凝視著這個按鈕,在按鈕被點擊之前,忽略掉其他任何的命令。同時,用戶不能選擇其他任何東西。看看現(xiàn)在這樣的情況?這就是異步編程在 JavaScript 為什么如此重要。
但是真正理解在異步操作過程中發(fā)生了什么,我們需要理解另外一個東西-事件循環(huán)。
事件循環(huán)
想象事件循環(huán),可以想象 JavaScript 是一個 todo-list 的管家。這個列表包含了所有你告訴它的事情。JavaScript 會按照你給的順序,一步步的遍歷這個列表。
假設(shè)你給JavaScript 的5個命令如下:
const addOne = (n) => n + 1 addOne(1) // 2 addOne(2) // 3 addOne(3) // 4 addOne(4) // 5 addOne(5) // 6
這將會出現(xiàn)在 JavaScript 的todo 列表里。
命令在 JavaScript 的 todo 列表里同步顯示。
除了 todo 列表,JavaScript 還保存了一個 waiting 列表,這個列表可以跟蹤需要等待的東西。如果你告訴 JavaScript 需要定披薩,它會給披薩店打電話,并把"等待披薩送來"加到等到列表里。同時,它會做 todo 列表已經(jīng)有的事情。
所以,想象一下有這樣的代碼。
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()
JavaScript 的初始列表將會是:
定披薩,拖地和熨燙衣服!
這是,當執(zhí)行到orderPizza
,JavaScript 知道需要等待披薩送來。因此,在把"等待披薩送來"加到等待列表中的同時會處理剩下的工作。
JavaScript 等待披薩到達。
當披薩送到時,按門鈴會通知 JavaScript并做一個標記,當處理完其他雜事時,會去執(zhí)行layTheTable
。
JavaScript 知道通過標記里的命令需要去執(zhí)行layTheTable
。
然后,一旦處理完了其他的雜務(wù),JavaScript 就會執(zhí)行回調(diào)函數(shù)layTheTable
。
當其他一切都完成時, JavaScript 會將其放置。
這就是我的朋友,事件循環(huán)。你可以用事件循環(huán)中的實際關(guān)鍵字來替代我們的巴特勒類比來理解所有的事情。
Todo-list-> Call stack
Waiting-list-> Web apis
Mental note-> Event queue
JavaScript 事件循環(huán)
如果你有20分鐘空閑時間的話,我強烈推薦你看Philip Roberts在 JSConf 上關(guān)于事件循環(huán)的演講。它會幫助你了解事件循環(huán)里的細節(jié)。
哦。我們在事件循環(huán)上轉(zhuǎn)了個大圈?,F(xiàn)在我們回頭來看。
之前,我們提到如果 JavaScript 專注地盯著一個按鈕并忽略其他所有的命令,這是非常糟糕的。是吧?
通過異步回調(diào),我們可以提前給 JavaScript 指令而不需要停止整個操作。
現(xiàn)在,當你讓 JavaScript 監(jiān)聽一個按鈕的點擊事件時,它將"監(jiān)聽按鈕"放在等待列表里,然后繼續(xù)做家務(wù)。當按鈕最終獲取到點擊事件時,JavaScript 會激活回調(diào),然后繼續(xù)運行
下面是一些常見的回調(diào)函數(shù),告訴 JavaScript 應(yīng)該怎么做:
當事件被觸發(fā)(比如:addEventListener
)
Ajax 執(zhí)行之后(比如: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))
這就是回調(diào)!
希望,你現(xiàn)在已經(jīng)弄清楚了回調(diào)是什么并且怎么去使用。在最開始的時候,你沒必要創(chuàng)建很多的回調(diào),更多的去專注于學(xué)習如何使用可用的回調(diào)函數(shù)。
現(xiàn)在,在結(jié)束之前,我們來看看回調(diào)的第一個問題 - 回調(diào)地獄
回調(diào)地獄是在多個回調(diào)嵌套出現(xiàn)時的一個現(xiàn)象。它發(fā)生在一個異步回調(diào)執(zhí)行依賴上一個異步回調(diào)執(zhí)行的時候。這些嵌套的回調(diào)會導(dǎo)致代碼非常難以理解。
在我的經(jīng)驗里,你只會在 Node.js 里看到回調(diào)地獄。當你的 JavaScript 在前臺運行時一般都不會遇到回調(diào)地獄。
這里有一個回調(diào)地獄的例子:
// 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}) }) } }) })
現(xiàn)在,對你來說,解讀上面的代碼是一個挑戰(zhàn)。相當?shù)碾y,不是么?難怪在看到嵌套回調(diào)時,開發(fā)人員會不寒而栗。
解決回調(diào)的一個解決方案是將回調(diào)函數(shù)分解成更小的部分,以減少嵌套代碼的數(shù)量
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 版本里,還有一些新的解決回調(diào)地獄的方法,比如: promises
和 async/await
。但是,會在另一個話題中解析它們。
看完了這篇文章,相信你對javascript中的回調(diào)是什么有了一定的了解,想了解更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!