URLhttps://learnscript.net/zh-hant/programming/operations/callbacks-and-delegates/
    複製連結移至說明  範例

    回呼,委派,事件介紹

    閱讀 7:50·字數 2355·發佈 
    Youtube 頻道
    訂閱 375

    本節中提到函式的部分,同樣適用於類別或執行個體的方法。

    回呼

    回呼(Callback)是指將一部分程式碼邏輯以某種形式,通常是函式,交付給另一方,而另一方會在某種情況下執行交付的程式碼。如果交付是以函式形式進行的,那麽這些函式也被稱為回呼函式,當然,回呼並非只能采用回呼函式,語言可以拓展出任意一種設計,比如委派。

    為何需要回呼?

    使用回呼的原因在於,雖然知道需要執行的邏輯,但無法確定執行的具體時間,這往往是非同步執行造成的。比如,我們知道需要在資料上傳完成後通知使用者,但無法確定上傳完成的具體時間,因為上傳采用了非同步的方式。

    什麽是非同步和同步?

    非同步不會保留控製權,當你呼叫支援非同步的函式時,並不會產生阻塞,這意味著可以立即進入下一行程式碼,不必等候函式的執行結果,非同步通常是由多執行緒來實作的。與非同步相反的是同步,同步函式會保留控製權,這意味著必須等候函式執行完畢,才能進入下一行程式碼。

    需要說明的是,這裏提到的函式並不是回呼函式,而是對應了另一方,回呼函式往往會作為這些函式的引數。

    什麽是執行緒和處理序?

    執行緒被包含在處理序內,你可以將其理解為實際執行的程式碼,一個處理序可以擁有多個執行緒。執行緒通過序列的方式執行其包含的程式碼,一行程式碼執行完畢後,才會進入另一行。而多個執行緒可以被平行執行,不需要彼此等候,並共用處理序內的資源。雖然在邏輯上具有平行的效果,但執行緒仍可能以並行的方式取得 CPU 的使用權,因為不存在無限的資源來實作真正的同時執行。

    通過多執行緒,你可以實作非同步操作,以避免不必要的等候時間。比如,我們可以建立一個新的執行緒來執行耗時的網路儲存工作,以便使用者在此期間進行其他操作。

    處理序可以被理解為作業系統中執行的程式或應用,比如,開啟的記事本應用

    在下面的 JavaScript 程式碼中,message會作為回呼函式傳遞給setTimeout,並在3秒後顯示訊息。

    callbacks_and_delegates.js
    // 回呼函式 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 運算式為showMessageshowData指派時,兩個匿名函式的簽章與委派Loaded規定的簽章保持了一致。

    *.cs
    // 委派 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); }
    callbacks_and_delegates.cs
    // delegate 是一個語法糖,他會轉換為一個類別的定義
    delegate void Loaded(string data);
    資料下載完畢!
    資料內容為:好吧,假設這是從伺服器取得的資料!

    事件

    從功能的角度講,事件是委派在特定案例下的另一種稱呼,即使兩者的程式碼書寫存在差異,實作互換也是完全有可能的。

    事件和委派之間的區別

    如果將事件和委派視為是不同的,那麽他們之間的區別可能如下。委派傾向於一次性效果,並作為另一方完成工作的必須部分。比如,在非同步取得資料時,我們可以通過委派告知取得資料後的下一步操作,否則單純的取得將毫無意義。事件傾向於持久效果,不是另一方的必須部分。比如,按鈕控製項通常具有的點選事件,是否為該事件編寫程式碼並不影響按鈕自身的正常運作。

    C# 類別MiddleSchool擁有事件OnMessage,該事件會在方法ClassBegin被呼叫時觸發。

    callbacks_and_delegates.cs
    // 類別 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方法被呼叫,可以看到這些程式碼的執行效果。

    *.cs
    // 建立 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