[英]JavaScript: How does a callback function work?
我對 JS 真的很陌生,我在編寫/理解回調函數時遇到了很多麻煩讓我們說,例如,我有以下代碼,但我不想要
takeNumbersGreaterThan(5);
執行到
insertNumbers();
完成了
numbers = [];
greaterThan = [];
insertNumbers();
takeNumbersGreaterThan(5);
insertNumbers(){
for (var i = 0; i<11; i++)
{
numbers.push(i)
}
}
takeNumbersGreaterThan(number){
for (var m = 0; m<numbers.length; m++)
{
if (numbers[m] > number)
{
greaterThan.push(numbers[m])
}
}
}
我該怎么做?
要了解回調,您必須先了解函數。 要理解 JavaScript 中的函數,您首先必須理解變量、值和函數。
幾乎所有的編程語言都可以處理值。 所以如果你做過任何編程,你就會對什么是值有一個基本的了解(我將在這里大大簡化值的類型,並將值和引用/指針都稱為“值”)。
價值是一種東西。 例如一個數字或一個字符串。 所以22.31
是一個值, "Hello Dave"
是一個值。
大多數語言也有變量的概念(但並非所有語言都有)。 變量是我們賦予值的“名稱”,以便更輕松地處理值。 例如,在下面, x
是一個變量:
var x = 12;
.. 它的值為 12。
變量允許我們做什么? 它允許我們在計算中用一個值代替一個名稱。 就像數學一樣。 例如,如果x
是12
並且我們知道我們可以將1
添加到12
我們也可以這樣做:
x + 1
在 javascript 中,函數是值。 例如,在下面我們將一個函數分配給一個變量:
function a () {return "Hello"}
var y = a;
由於變量的作用是允許您用名稱替換值,因此如果您可以使用語法a()
調用函數a
,則意味着您也可以使用變量y
執行此操作:
y(); // returns "Hello"
如果函數是值,這也意味着您可以將函數作為參數傳遞給其他函數。 例如,以下是您通常在另一個函數中調用函數的方式:
function a () {return "Hello"}
function b () {return a() + " World"}
b(); // returns "Hello World"
如果您可以將函數作為變量傳遞,則意味着您可以執行以下操作:
function a () {return "Hello"}
function b () {return "Bye"}
function c (functionVariable) {return functionVariable() + " World"}
c(a); // returns "Hello World"
c(b); // returns "Bye World"
如你看到的。 回調根本沒有什么特別之處。 它們只是在 javascript 函數中遵循與其他值(如數字、數組和字符串)相同的規則這一事實的結果。
從上面的示例中可以看出,對函數c
兩次調用都返回一個值。 因此,即使函數c
接受回調,它也不是異步的。 因此回調可用於同步和異步代碼。
同步回調的一個很好的例子是Array.sort()
方法。 它對數組進行排序,但接受一個可選的回調來定義如何排序(按字母順序、數字順序、姓氏等)。
現在忘記 ajax 或網絡代碼。 讓我們來看看一個場景,讓異步代碼使用回調的原因更加明顯。
比如說你有一個按鈕。 現在,當用戶單擊此按鈕時,您希望發生某些事情。 你是怎樣做的?
大多數人做的第一件事可能是這樣的:
while (1) {
if (button.isClicked) {
doSomething();
}
}
好的。 所以這是一個無限循環,它只檢查按鈕是否被點擊,而不檢查其他任何東西。 那么你期望瀏覽器如何更新 UI 和跟蹤鼠標呢? 這將我們引向人們嘗試做的下一件事:
while (1) {
if (button.isClicked) {
doSomething();
}
else {
updateUI();
}
}
好的。 偉大的。 但是有兩個問題。 首先,如果有人要編寫像 Google Charts 或 jQuery 之類的庫或任何與 UI 相關的東西,他們要么編寫自己的while(1)...
循環,要么您必須手動將它們的函數復制/粘貼到 while 循環中。 這沒有規模。 其次,更重要的是,這是低效的。 該 while 循環將使用 100% CPU 時間檢查按鈕。 如果按鈕被點擊時瀏覽器可以告訴我們,那不是更好嗎?
幸運的是,在 javascript 中,函數只是值。 您可以將一個函數傳遞給瀏覽器,並告訴它在有人單擊按鈕時執行您的函數:
button.addEventListener('click', doSomething);
注意:請注意將函數視為變量和調用函數之間的區別。 如果要將函數視為變量,只需使用名稱即可。 如果你想調用一個函數,請使用像
doSomething()
這樣的大括號。
每個人似乎都堅持制作異步 API 有兩個原因,尤其是在像 javascript 這樣的語言中。
首先,低級文件和網絡 I/O 是異步的。 這意味着如果您想與數據庫或服務器對話或讀取文件,您需要將其實現為異步。 此外,javascript 是單線程的。 因此,如果您使用 I/O 函數的同步版本,您將凍結其他所有內容。
其次,事實證明,在很多情況下(但肯定不是全部)異步單線程編程與同步多線程編程一樣快,有時甚至更快。
綜合以上兩個原因,在js社區造成了社會壓力,需要確保所有的I/O代碼都是異步的,以保持速度優勢,而不是阻塞別人的代碼。
如果我理解正確,您想了解更多有關回調的信息並想使用它們。 讓我也嘗試幫助您使用您的代碼。
如果要執行takeNumbersGreaterThan(5);
在insertNumbers();
使用回調函數,你可以做這樣的事情:
numbers = [];
greaterThan = [];
insertNumbers(takeNumbersGreaterThan, 5);
function insertNumbers(callbackFunction, callbackFunctionParam){
for (var i = 0; i<11; i++)
{
numbers.push(i)
}
callbackFunction(callbackFunctionParam);
}
function takeNumbersGreaterThan(number){
for (var m = 0; m<numbers.length; m++)
{
if (numbers[m] > number)
{
greaterThan.push(numbers[m])
}
}
}
但這只是一個簡單的例子,說明如何在一些計算后調用回調函數。 這段代碼可以改進。 關鍵是,您可以將回調函數作為參數傳遞給函數,然后再執行此回調函數。
你已經在那里了。 您的代碼幾乎完全正確。 您只是缺少功能鍵聲明。
下面的腳本向您展示了如何在 insertNumbers 之后運行 takeNumbersGreaterThan。 在我的示例中,我還更改了函數符號,以便將數組作為參數傳遞並避免一些稱為閉包的常見“錯誤”。
var numbers = []; var greaterThan = []; var insertNumbers = function(numbers) { for (var i = 0; i<11; i++) numbers.push(i) } var takeNumbersGreaterThan = function(number, numbers, greaterThan){ for (var m = 0; m<numbers.length; m++) { if (numbers[m] > number) greaterThan.push(numbers[m]); } } // run insert numbers insertNumbers(numbers); // run take numbers greater than takeNumbersGreaterThan(5, numbers, greaterThan); // log document.write(greaterThan);
您的代碼不使用任何異步調用,因此您不需要使用任何回調來處理執行。 但如果你想知道怎么做,這就是方法。
numbers = [];
greaterThan = [];
function insertNumbers(callback){
for (var i = 0; i<11; i++)
{
numbers.push(i)
}
callback(); // now execute the callback funtion
}
function takeNumbersGreaterThan(number){
for (var m = 0; m<numbers.length; m++)
{
if (numbers[m] > number)
{
greaterThan.push(numbers[m]);
}
}
console.log(greaterThan);
}
insertNumbers(function() { // here we send a functions as argument to insertNumbers which will execute when callback() is called
takeNumbersGreaterThan(5);
});
insertNumbers
接受一個名為“回調”的參數。 當insertNumbers
完成后,我們只需運行callback()
。 在對insertNumber
的初始調用中,我們傳遞了一個函數作為參數,該函數將在insertNumers
完成(或callback()
被調用)后立即執行。
代碼(大部分)是按順序執行的。 在您提供的代碼中,計算機按照您提供的順序運行代碼。 首先它創建一個新的數組對象並將其設置為 numbers 變量,然后它創建一個新的數組對象並將其設置為 GreaterThan 變量。 然后,它運行 insertNumbers 函數。 現在計算機所做的是跳轉到您在 insertNumbers 中定義的代碼並執行所有這些代碼。 然后,在它完成之后,它將返回執行它所在的初始代碼線程,該線程又回到第 4 行。所以現在它將跳轉到 takeNumbersGreaterThan 代碼。 因此,在功能上,您不需要任何回調,因為您的代碼不會執行任何需要任意時間的事情。
對此進行了解釋,您會看到 takeNumbersGreaterThan 直到執行 insertNumbers 后才會執行。
唯一不按順序執行代碼的時間是您開始執行多核/線程代碼時。
當某些事情需要任意時間時使用回調,例如當您從磁盤讀取數據或從網絡請求數據時。
回調可以存在,因為在 javascript(和許多其他語言)中定義的函數在代碼中作為對象存在。 如果您沒有在函數名后放置括號,您實際上是在引用函數對象,就像任何其他變量一樣。 所以你可以在你的代碼和其他代碼中傳遞這個函數對象。 這就是這個例子中發生的事情。
setTimeout(myCallback, 5000)
function myCallback(){
console.log("5 seconds have passed");
}
因此,如您所見,我可以將我的函數myCallback
交給另一個函數,在本例中為setTimeout
,以便在另一個函數完成任務后使用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.