您好,登錄后才能下訂單哦!
小編給大家分享一下javascript中的回調是什么,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討吧!
你有無意中看到 "callback" 但并不知道其中的意思么?不用擔心。不是只有你一個人這樣。很多JavaScript 新手都難以理解回調。
雖然回調比較令人困惑,你仍然需要徹底的學習理解它們,因為它在 JavaScript 中是一個很關鍵的概念。如果你不知道回調,那么你無法走的長遠。
這篇文章中你會看到 ES6 里的箭頭函數。如果你還不熟悉它們,我建議你先看看ES6 post。(只要閱讀箭頭函數部分)。
回調是一個函數,會作為一個參數傳遞到另一個函數中,并稍后去執行。(開發人員說在執行函數時調用另一個函數,這就是為什么 callbacks
稱之為回調的原因)。
它們在 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
函數。所以,在這個例子中,當addEventListener
函數接收一個回調函數時,clicked
是一個回調。
現在知道回調是什么了么?:)
我們來看看另外一個例子。這一次,我們假設你想過濾一個數字數組來得到一個小于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() })
唷!這是回調的基本含義!只要記住關鍵字:將一個函數傳遞到另一個函數中,你將回想起上面提到的機制。
這種傳遞函數的能力是一個很大的事情。它是如此之大,以至于 JavaScript 中的函數都是高階函數。高階函數是函數式編程范式中非常重要的東西。
但我們現在并不討論這個話題。現在,我確信你已經知道了回調以及如何使用了。但是,你為什么需要使用回調呢?
回調有二種不同的使用方式 - 在同步函數和在異步函數中。
同步函數中的回調
如果你的代碼執行是一個從上到下,從做到右的方式,順序地,在下一行代碼執行前會等到代碼執行完成,那么你的代碼是同步的。
我們來看個例子,以便于更早的理解:
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 seconds passed!”。
同時,在等到10秒去執行setTimeout
時,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 的5個命令如下:
const addOne = (n) => n + 1 addOne(1) // 2 addOne(2) // 3 addOne(3) // 4 addOne(4) // 5 addOne(5) // 6
這將會出現在 JavaScript 的todo 列表里。
命令在 JavaScript 的 todo 列表里同步顯示。
除了 todo 列表,JavaScript 還保存了一個 waiting 列表,這個列表可以跟蹤需要等待的東西。如果你告訴 JavaScript 需要定披薩,它會給披薩店打電話,并把"等待披薩送來"加到等到列表里。同時,它會做 todo 列表已經有的事情。
所以,想象一下有這樣的代碼。
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 的初始列表將會是:
定披薩,拖地和熨燙衣服!
這是,當執行到orderPizza
,JavaScript 知道需要等待披薩送來。因此,在把"等待披薩送來"加到等待列表中的同時會處理剩下的工作。
JavaScript 等待披薩到達。
當披薩送到時,按門鈴會通知 JavaScript并做一個標記,當處理完其他雜事時,會去執行layTheTable
。
JavaScript 知道通過標記里的命令需要去執行layTheTable
。
然后,一旦處理完了其他的雜務,JavaScript 就會執行回調函數layTheTable
。
當其他一切都完成時, JavaScript 會將其放置。
這就是我的朋友,事件循環。你可以用事件循環中的實際關鍵字來替代我們的巴特勒類比來理解所有的事情。
Todo-list-> Call stack
Waiting-list-> Web apis
Mental note-> Event queue
JavaScript 事件循環
如果你有20分鐘空閑時間的話,我強烈推薦你看Philip Roberts在 JSConf 上關于事件循環的演講。它會幫助你了解事件循環里的細節。
哦。我們在事件循環上轉了個大圈。現在我們回頭來看。
之前,我們提到如果 JavaScript 專注地盯著一個按鈕并忽略其他所有的命令,這是非常糟糕的。是吧?
通過異步回調,我們可以提前給 JavaScript 指令而不需要停止整個操作。
現在,當你讓 JavaScript 監聽一個按鈕的點擊事件時,它將"監聽按鈕"放在等待列表里,然后繼續做家務。當按鈕最終獲取到點擊事件時,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))
這就是回調!
希望,你現在已經弄清楚了回調是什么并且怎么去使用。在最開始的時候,你沒必要創建很多的回調,更多的去專注于學習如何使用可用的回調函數。
現在,在結束之前,我們來看看回調的第一個問題 - 回調地獄
回調地獄是在多個回調嵌套出現時的一個現象。它發生在一個異步回調執行依賴上一個異步回調執行的時候。這些嵌套的回調會導致代碼非常難以理解。
在我的經驗里,你只會在 Node.js 里看到回調地獄。當你的 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中的回調是什么有了一定的了解,想了解更多相關知識,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。