回呼,委派,事件介紹
訂閱 375
本節中提到函式的部分,同樣適用於類別或執行個體的方法。
回呼
回呼(Callback)是指將一部分程式碼邏輯以某種形式,通常是函式,交付給另一方,而另一方會在某種情況下執行交付的程式碼。如果交付是以函式形式進行的,那麽這些函式也被稱為回呼函式,當然,回呼並非只能采用回呼函式,語言可以拓展出任意一種設計,比如委派。
為何需要回呼?
使用回呼的原因在於,雖然知道需要執行的邏輯,但無法確定執行的具體時間,這往往是非同步執行造成的。比如,我們知道需要在資料上傳完成後通知使用者,但無法確定上傳完成的具體時間,因為上傳采用了非同步的方式。
什麽是非同步和同步?
非同步不會保留控製權,當你呼叫支援非同步的函式時,並不會產生阻塞,這意味著可以立即進入下一行程式碼,不必等候函式的執行結果,非同步通常是由多執行緒來實作的。與非同步相反的是同步,同步函式會保留控製權,這意味著必須等候函式執行完畢,才能進入下一行程式碼。
需要說明的是,這裏提到的函式並不是回呼函式,而是對應了另一方,回呼函式往往會作為這些函式的引數。
什麽是執行緒和處理序?
執行緒被包含在處理序內,你可以將其理解為實際執行的程式碼,一個處理序可以擁有多個執行緒。執行緒通過序列的方式執行其包含的程式碼,一行程式碼執行完畢後,才會進入另一行。而多個執行緒可以被平行執行,不需要彼此等候,並共用處理序內的資源。雖然在邏輯上具有平行的效果,但執行緒仍可能以並行的方式取得 CPU 的使用權,因為不存在無限的資源來實作真正的同時執行。
通過多執行緒,你可以實作非同步操作,以避免不必要的等候時間。比如,我們可以建立一個新的執行緒來執行耗時的網路儲存工作,以便使用者在此期間進行其他操作。
處理序可以被理解為作業系統中執行的程式或應用,比如,開啟的記事本應用。
在下面的 JavaScript 程式碼中,message
會作為回呼函式傳遞給setTimeout
,並在3
秒後顯示訊息。
// 回呼函式 message,顯示一條訊息
function message() {
console.log('一條訊息?!')
}
// 函式 setTimeout 將在 3 秒後呼叫 message
setTimeout(message, 3000)
// console.log 會緊接著 setTimeout 執行
console.log('請等候 3 秒鐘!')
請等候 3 秒鐘!
約 3 秒後…
一條訊息?!
JavaScript 的 setTimeout 是否為非同步函式?
是的,無論 JavaScript 的執行環境是否支援多執行緒,setTimeout
都是一個非同步函式。一些觀點以執行緒數量為依據持相反的觀點,但非同步和多執行緒並沒有必然的聯系,多執行緒只是實作非同步的方式之一。
委派
與回呼函式類似,委派可以向另一方交付程式碼邏輯,他包含了對函式或方法的參考。為了提供一定的規格,委派可以指定簽章,以排除不符合要求的函式或方法。
在一些語言中,比如 C#,委派是通過類別來實作的,因此對比回呼函式,委派提供了更多的可操作性,其中最為常見的是委派合併。
什麽是委派合併?
委派合併也被稱為委派多點傳送,他所實作的效果是,將多個委派執行個體合併,這些執行個體所包含的程式碼邏輯將被組合在一起。
當然,委派移除提供了與合併相反的操作。
函式
想要了解簽章,你可以檢視函式簽章一段。
在下面的 C# 程式碼中,我們建立了委派Loaded
的兩個執行個體並將他們合併為loaded
,然後傳遞給函式Load
。在使用 Lambda 運算式為showMessage
和showData
指派時,兩個匿名函式的簽章與委派Loaded
規定的簽章保持了一致。
// 委派 Loaded 的執行個體 showMessage
Loaded showMessage = data => Console.WriteLine("資料下載完畢!");
// 委派 Loaded 的執行個體 showData
Loaded showData = data => Console.WriteLine($"資料內容為:{data}");
// 合併 showMessage,showData
Loaded loaded = showMessage + showData;
// 將 loaded 傳遞給函式 Load
Load(loaded);
// 函式 Load,用於從伺服器下載資料
void Load(Loaded loaded) {
// 這裏假設 data 是從伺服器取得的資料
string data = "好吧,假設這是從伺服器取得的資料!";
// 呼叫 loaded 所包含的匿名函式
loaded.Invoke(data);
}
// delegate 是一個語法糖,他會轉換為一個類別的定義
delegate void Loaded(string data);
資料下載完畢!
資料內容為:好吧,假設這是從伺服器取得的資料!
事件
從功能的角度講,事件是委派在特定案例下的另一種稱呼,即使兩者的程式碼書寫存在差異,實作互換也是完全有可能的。
事件和委派之間的區別
如果將事件和委派視為是不同的,那麽他們之間的區別可能如下。委派傾向於一次性效果,並作為另一方完成工作的必須部分。比如,在非同步取得資料時,我們可以通過委派告知取得資料後的下一步操作,否則單純的取得將毫無意義。事件傾向於持久效果,不是另一方的必須部分。比如,按鈕控製項通常具有的點選事件,是否為該事件編寫程式碼並不影響按鈕自身的正常運作。
C# 類別MiddleSchool
擁有事件OnMessage
,該事件會在方法ClassBegin
被呼叫時觸發。
// 類別 MiddleSchool,表示中學
class MiddleSchool
{
// 事件 OnMessage,表示學校發出了訊息
public event EventHandler? OnMessage;
// 方法 ClassBegin,開始上課
public void ClassBegin()
{
// 觸發事件,外部將收到來自學校的訊息
OnMessage?.Invoke(this, new MessageArgs() { Message = "上課了!" });
}
}
// 類別 MessageArgs,事件參數,包含了訊息的內容
class MessageArgs : EventArgs
{
// 訊息的內容
public string Message = string.Empty;
}
建立MiddleSchool
類別的執行個體school
,並為其編寫OnMessage
事件的程式碼,隨著ClassBegin
方法被呼叫,可以看到這些程式碼的執行效果。
// 建立 MiddleSchool 類別的執行個體
MiddleSchool school = new();
// 為事件 OnMessage 編寫程式碼
school.OnMessage += (object? sender, EventArgs e) =>
Console.WriteLine($"接到了學校的訊息:{(e as MessageArgs)?.Message}");
// 呼叫 ClassBegin,事件 OnMessage 將被觸發
school.ClassBegin();
接到了學校的訊息:上課了!
原始碼
callbacks_and_delegates.js·codebeatme/programming·GitHub
callbacks_and_delegates.cs·codebeatme/programming·GitHub