C# 基礎知識系列]專題七:事件和消息的機制理解

消息/事件機制是幾乎所有開發語言都有的機制,並不是deviceone的獨創,在某些語言稱之爲消息(Event),有些地方稱之爲(Message). 其實原理是類似的,只不過有些實現的方式要複雜一點。我們deviceone統一就叫消息.

消息基礎概念

還有一些初學者不太熟悉這個機制,我們先簡單介紹一些基礎概念,如果熟悉的人可以跳過這個部分。
一個/條消息可以理解爲是一個數據結構,包含以下幾個基本部分:

  1. 消息源 :就是消息的來源,發出這個消息的對象
  2. 消息名 :就是消息的唯一標示
  3. 消息數據:消息發出後附帶的數據,有可能數據是空

消息從種類上又可以分爲2種:

  1. 系統消息 :由操作系統或deviceone系統發送出來的消息,消息的名稱是固定的。
  2. 自定義消息:由開發者自己定義,自己發送出來的消息,消息的名字是隨意的,可以任意定義。

舉例說明:
比如用戶點擊一個do_Button按鈕,就會觸發一個系統消息,包含3個部分:

  1. 消息源:用戶點中的button對象
  2. 消息名:touch
  3. 消息數據:這個消息沒有附帶數據

比如用戶通過do_Button按鈕觸發一個自定義事件,包含3個部分:

  1. 消息源: button對象
  2. 消息名:用戶隨便定義,叫aaabbbccc都可以
  3. 消息數據:附帶的數據由觸發消息的時候設定

發佈/訂閱模式

發佈/訂閱模式是最常用的設計模式之一,是消息機制的核心,其特點就是降低耦合度,讓二個獨立的對象不互相依賴。簡單介紹一下,熟悉的同學可以跳過。
我們先從現實的一個簡單例子來說明這個問題,參考下圖:

從這個圖我們可以看出

  1. 消費者和出版社互相不認識,消費者不需要了解他想要的雜誌是具體哪家出版社出的;出版社也不需要了解具體是哪個人定了他們出版社發行的書。
  2. 消費者和出版社必須都認識郵局。
  3. 消費者需要告訴郵局消費者的名字地址以及想要訂閱的雜誌名字
  4. 可以多個消費者訂閱同一本雜誌
  5. 郵局拿到雜誌後,會一一通知消費者,通知的時候同時把雜誌送到消費者手裏。

看完上面現實例子,我們再來看抽象的描述會更清晰一點,看下圖:

和上面的實際例子描述一一對應:

  1. 系統/開發者和函數對象互相不依賴,系統/開發者只管觸發一個消息,並不關心誰去接受
  2. 系統/開發者和函數對象必須能獲取到消息源對象
  3. 函數對象訂閱消息的時候需要標示消息的名稱和函數對象的引用
  4. 可以多個函數對象訂閱同一個消息源同一名字的消息
  5. 消息源觸發消息會一一通知所有訂閱者,並把data數據傳遞到回調函數對象

看完抽象的描述,我們最後來看實際的deviceone開發的例子,還是以do_Button爲例子。

1. 當用戶點擊一個button,觸摸到的時候,系統會獲取到button這個對象作爲消息源,fire一個”touch”消息,任何訂閱了”touch”消息的函數對象都會接收到這個消息並引起函數的執行。
  1. //獲取button對象
  2. var btn_hello = ui("btn_hello");
  3. //定義函數對象
  4. function f1(){
  5. //當btn_hello這個按鈕接收到手指點擊就會執行下面的代碼
  6. deviceone.print("f1 函數接收到點擊觸發消息")
  7. }
  8. function f2(){
  9. //當btn_hello這個按鈕接收到手指點擊就會執行下面的代碼
  10. deviceone.print("f2 函數接收到點擊觸發消息")
  11. }
  12. //f1,f2訂閱button的touch消息
  13. btn_hello.on("touch",f1);
  14. btn_hello.on("touch",f2);
2. 我們可以爲button對象定義2個自定義的消息”message1”和”message2”,分別有2個函數對象訂閱這2個消息。但是最後要觸發這個消息必須是開發者通過調用fire函數才能觸發,這就是和系統消息的區別。
  1. //獲取button對象
  2. var btn_hello = ui("btn_hello");
  3. //定義函數對象
  4. function f1(d){
  5. //當btn_hello這個按鈕接收到開發者觸發的消息message1就會執行下面的代碼
  6. deviceone.print("f1 函數接收到message1消息,消息的數據是:"+d)
  7. }
  8. function f2(d){
  9. //當btn_hello這個按鈕接收到開發者觸發的消息message2就會執行下面的代碼
  10. deviceone.print("f2 函數接收到message2消息,消息的數據是:"+d)
  11. }
  12. //f1,f2訂閱button的touch消息
  13. btn_hello.on("message1",f1);
  14. btn_hello.on("message2",f2);
  15. //觸發消息
  16. btn_hello.fire("message1","data1");
  17. btn_hello.fire("message2","data2");

看到這裏,你肯定會奇怪,爲什麼我們要在button上自定義對象?這有神馬意義?其實確實沒有意義也沒有必要,這裏只是拿button舉例子,在常規的開發中,基本不會這麼用。

消息的使用

前面講了這麼多,現在纔是deviceone消息的使用。使用其實很簡單,上面的例子基本說明的了系統事件和自定義事件的使用方法。
有幾個概念再說明一下

1.deviceone的所有對象,包括UI,MM,SM對象都可以是消息源
  1. // SM對象可以是消息源
  2. var page = sm("do_Page");
  3. page.on("loaded",function()){
  4. // 這個是page對象的系統消息,這個消息不需要手動觸發,系統會自動觸發
  5. }
  6. page.on("message1",function(d)){
  7. // 這個是page對象的自定義消息
  8. }
  9. page.fire("message1","data1");
  10. // MM對象可以是消息源
  11. var http = mm("do_Http");
  12. http.on("result",function()){
  13. // 這個是http對象的系統消息,這個消息不需要手動觸發,接受到http服務端的反饋後會自動觸發
  14. }
  15. http.on("message1",function(d)){
  16. // 這個是http對象的自定義消息
  17. }
  18. http.fire("message1","data1");
  19. //UI對象可以是消息源
  20. var alayout = ui("alayout_id1");
  21. alayout.on("touch",function()){
  22. // 這個是alayout對象的系統消息,這個消息不需要手動觸發,手機點擊就會觸發
  23. }
  24. alayout.on("message1",function(d)){
  25. // 這個是alayout對象的自定義消息
  26. }
  27. alayout.fire("message1","data1");
2.消息源對象有作用域,所以訂閱和觸發的消息源必須是是一個作用域的同一個對象。這裏結合數據分享和數據傳遞文檔來理解。

看以下的例子,test1.ui和test2.ui有可能在一個page作用域,也有可能不在一個作業域,只有在一個作用域fire的消息才能正確送達回調函數。
判斷是否一樣,可以通過打印page的地址 page.getAddress().

  1. //在test1.ui.js裏訂閱消息
  2. var page = sm("do_Page");
  3. deviceone.print(page.getAddress());
  4. page.on("message1",function(d)){
  5. deviceone.print(d);
  6. }
  7. //在test2.ui.js觸發消息
  8. var page = sm("do_Page");
  9. deviceone.print(page.getAddress());
  10. page.fire("message1","data1");

如果不在同一page作用域,則可以把消息訂閱在2個page都能共享到的app作用域
上面的代碼改成:

  1. //在test1.ui.js裏訂閱消息
  2. var app = sm("do_App");
  3. app.on("message1",function(d)){
  4. deviceone.print(d);
  5. }
  6. //在test2.ui.js觸發消息
  7. var app = sm("do_App");
  8. app.fire("message1","data1");
3.同樣的函數對象可以重複訂閱一個對象源的消息,觸發消息的時候會使函數執行多次,這是初學者經常犯的錯誤。
  1. var page = sm("do_Page");
  2. var count = 0;
  3. function f(){
  4. deviceone.print("執行次數"+(count++));
  5. }
  6. page.on("message1",f);
  7. page.on("message1",f);
  8. page.fire("message1");

看上面的例子,如果執行的話,會打印2此,因爲訂閱了2次,或許你會說誰會寫這樣的代碼?實際情況肯定沒有這麼容易看出來執行了重複的on函數,實際情況經常是比如在點擊事件裏執行on函數,每點擊一下按鈕,就重複訂閱一次。

4.消息的訂閱一定要在消息的觸發之前,這是初學者經常犯的錯誤。
  1. var page = sm("do_Page");
  2. var count = 0;
  3. function f(){
  4. deviceone.print("執行次數"+(count++));
  5. }
  6. page.fire("message1");
  7. page.on("message1",f);

看上面的例子,如果執行的話,會沒有效果,或許你會說誰會寫這樣的代碼?實際情況肯定沒有這麼容易看出來順序反了,實際情況經常是比如on函數執行在某一個函數的回調函數裏,你無法確定回調函數啥時候執行,是否是在fire之前執行。一般碰到這種情況可以加幾個deviceone.print打印一下看看是on先執行還是fire先執行。

5.有訂閱就有取消訂閱,取消訂閱是off函數,之所以很少用,是因爲closePage的時候會自動把當前page作用域訂閱的消息全部釋放。但是如果消息訂閱在app作用域,就要注意,可能需要手動去取消訂閱。否則就會出現觸發消息的時候會使函數執行多次的問題。
  1. var page = sm("do_Page");
  2. var count = 0;
  3. function f(){
  4. deviceone.print("執行次數"+(count++));
  5. }
  6. page.on("message1",f);
  7. page.fire("message1");
  8. page.off("message1");
  9. page.fire("message1");

看上面的例子,打印只會執行一次,因爲fire一次後就取消訂閱了。

發佈了44 篇原創文章 · 獲贊 73 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章