1. 程式人生 > 程式設計 >一篇文章看懂JavaScript中的回撥

一篇文章看懂JavaScript中的回撥

前言

回撥函式是每個前端程式設計師都應該知道的概念之一。回撥可用於陣列、計時器函式、promise、事件處理中。

本文將會解釋回撥函式的概念,同時幫你區分兩種回撥:同步和非同步。

回撥函式

首先寫一個向人打招呼的函式。

只需要建立一個接受 name 引數的函式 greet(name)。這個函式應返回打招呼的訊息:

function greet(name) {
 return `Hello,${name}!`;
}

greet('Cristina'); // => 'Hello,Cristina!'

如果向很多人打招呼該怎麼辦?可以用特殊的陣列方法 array.map() 可以實現:

const persons = ['Cristina','Ana'];

const messages = persons.map(greet);
messages; // => ['Hello,Cristina!','Hello,Ana!'] 

persons.map(greet) 獲取 persons 陣列的所有元素,並分別用每個元素作為呼叫引數來呼叫 greet() 函式:greet('Cristina'),greet('Ana')。

有意思的是 persons.map(greet) 方法可以接受 greet() 函式作為引數。這樣 greet() 就成了回撥函式。

persons.map(greet) 是用另一個函式作為引數的函式,因此被稱為高階函式。

回撥函式作為高階函式的引數,高階函式通過呼叫回撥函式來執行操作。

重要的是高階函式負責呼叫回撥,併為其提供正確的引數。

在前面的例子中,高階函式 persons.map(greet) 負責呼叫 greet() 函式,並分別把陣列中所有的元素 'Cristina' 和 Ana ' 作為引數。

這就為識別回撥提供了一條簡單的規則。如果你定義了一個函式,並將其作引數提供給另一個函式的話,那麼這就建立了一個回撥。

你可以自己編寫使用回撥的高階函式。下面是 array.map() 方法的等效版本:

function map(array,callback) {
 const mappedArray = [];
 for (const item of array) { 
 mappedArray.push(
 callback(item) );
 }
 return mappedArray;
}

function greet(name) {
 return `Hello,${name}!`;
}

const persons = ['Cristina','Ana'];

const messages = map(persons,greet);messages; // => ['Hello,Ana!'] 

map(array,callback) 是一個高階函式,因為它用回撥函式作為引數,然後在其主體內部呼叫該回調函式:callback(item)。

注意,常規函式(用關鍵字 function 定義)或箭頭函式(用粗箭頭 => 定義)同樣可以作為回撥使用。

同步回撥

回撥的呼叫方式有兩種:同步和非同步回撥。

同步回撥是“阻塞”的:高階函式直到回撥函式完成後才繼續執行。

例如,呼叫 map() 和 greet() 函式。

function map(array,callback) {
 console.log('map() starts');
 const mappedArray = [];
 for (const item of array) { mappedArray.push(callback(item)) }
 console.log('map() completed');
 return mappedArray;
}

function greet(name) {
 console.log('greet() called');
 return `Hello,${name}!`;
}

const persons = ['Cristina'];

map(persons,greet);
// logs 'map() starts'
// logs 'greet() called'
// logs 'map() completed'

其中 greet() 是同步回撥。

同步回撥的步驟:

  1. 高階函式開始執行:'map() starts'
  2. 回撥函式執行:'greet() called'
  3. .最後高階函式完成它自己的執行過程:'map() completed'

同步回撥的例子

許多原生 JavaScript 型別的方法都使用同步回撥。

最常用的是 array 的方法,例如:array.map(callback),array.forEach(callback),array.find(callback),array.filter(callback),array.reduce(callback,init)

// Examples of synchronous callbacks on arrays
const persons = ['Ana','Elena'];

persons.forEach(
 function callback(name) { console.log(name);
 }
);
// logs 'Ana'
// logs 'Elena'

const nameStartingA = persons.find(
 function callback(name) { return name[0].toLowerCase() === 'a';
 }
);
nameStartingA; // => 'Ana'

const countStartingA = persons.reduce(
 function callback(count,name) { const startsA = name[0].toLowerCase() === 'a';
 return startsA ? count + 1 : count;
 },0
);
countStartingA; // => 1

字串型別的 string.replace(callback) 方法也能接受同步執行的回撥:

// Examples of synchronous callbacks on strings
const person = 'Cristina';

// Replace 'i' with '1'
person.replace(/./g,function(char) { return char.toLowerCase() === 'i' ? '1' : char;
 }
); // => 'Cr1st1na'

非同步回撥

非同步回撥是“非阻塞的”:高階函式無需等待回撥完成即可完成其執行。高階函式可確保稍後在特定事件上執行回撥。

在以下的例子中,later() 函式的執行延遲了 2 秒:

console.log('setTimeout() starts');
setTimeout(function later() {
 console.log('later() called');
},2000);
console.log('setTimeout() completed');

// logs 'setTimeout() starts'
// logs 'setTimeout() completed'
// logs 'later() called' (after 2 seconds)

later() 是一個非同步回撥,因為 setTimeout(later,2000) 啟動並完成了執行,但是 later() 在 2 秒後執行。

非同步呼叫回撥的步驟:

  1. 高階函式開始執行:'setTimeout()starts'
  2. 高階函式完成其執行:'setTimeout() completed'
  3. 回撥函式在 2 秒鐘後執行:'later() called'

非同步回撥的例子

計時器函式非同步呼叫回撥:

setTimeout(function later() {
 console.log('2 seconds have passed!');
},2000);
// After 2 seconds logs '2 seconds have passed!'

setInterval(function repeat() {
 console.log('Every 2 seconds');
},2000);
// Each 2 seconds logs 'Every 2 seconds!' 

DOM 事件偵聽器還非同步呼叫事件處理函式(回撥函式的子型別):

const myButton = document.getElementById('myButton');

myButton.addEventListener('click',function handler() {
 console.log('Button clicked!');
});
// Logs 'Button clicked!' when the button is clicked

4.非同步回撥函式與非同步函式

在函式定義之前加上特殊關鍵字 async 會建立一個非同步函式:

async function fetchUserNames() {
 const resp = await fetch('https://api.github.com/users?per_page=5');
 const users = await resp.json();
 const names = users.map(({ login }) => login);
 console.log(names);
}

fetchUserNames() 是非同步的,因為它以 async 為字首。函式 await fetch('https://api.github.com/users?per_page=5') 從 GitHub 上獲取前5個使用者 。然後從響應物件中提取 JSON 資料:await resp.json()。

非同步函式是 promise 之上的語法糖。當遇到表示式 await <promise> (呼叫 fetch() 會返回一個promise)時,非同步函式會暫停執行,直到 promise 被解決。

非同步回撥函式和非同步函式是不同的兩個術語。

非同步回撥函式由高階函式以非阻塞方式執行。但是非同步函式在等待 promise(await <promise>)解析時會暫停執行。

但是你可以把非同步函式用作非同步回撥!

讓我們把非同步函式 fetch UserNames() 設為非同步回撥,只需單擊按鈕即可呼叫:

const button = document.getElementById('fetchUsersButton');

button.addEventListener('click',fetchUserNames);

總結

回撥是一個可以作為引數傳給另一個函式(高階函式)執行的函式。

回撥函式有兩種:同步和非同步。

同步回撥是阻塞的。

非同步回撥是非阻塞的。

最後考考你:setTimeout(callback,0) 執行 callback 時是同步還是非同步的?

到此這篇關於JavaScript中回撥的文章就介紹到這了,更多相關JavaScript的回撥內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!