訊息/事件機制幾乎是所有開發語言都有的機制,並不是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.訊息名稱:使用者隨便定義,叫aaa,bbb,ccc都可以
3.訊息資料:附帶的資料由觸發訊息的時候設定
發佈/訂閱模式
發布/訂閱模式是最常用的設計模式之一,是訊息機制的核心,其特點就是降低耦合度,讓二個獨立的物件不互相依賴。簡單介紹一下,熟悉的同學可以跳過。
我們先從現實的簡單例子來說明這個問題,參考下圖:
從這張圖我們可以看出
1.消費者和出版社互相不認識,消費者不需要了解他想要的雜誌是具體哪家出版社出的;出版社也不需要了解具體是哪個人定了他們出版社發行的書。
2.消費者和出版社必須都認識郵局。
3.消費者需要告訴郵局消費者的名字地址以及想要訂閱的雜誌名字
4.可以多個消費者訂閱同一本雜誌
5.郵局拿到雜誌後,會一一通知消費者,通知的時候同時把雜誌送到消費者手上。
看完上面現實例子,我們再來看抽象的描述會更清晰一點,看下圖:
和上面的實際例子描述一一對應:
1.系統/開發者和函數物件互相不依賴,系統/開發者只管觸發一個訊息,並不關心誰去接受
2.系統/開發者和函數物件必須能取得到訊息源物件
3.函數物件訂閱訊息的時候需要標示訊息的名稱和函數物件的參考
4.可以多個函數物件訂閱同一個訊息來源相同名字的訊息
5.訊息源觸發訊息會一一通知所有訂閱者,並把data資料傳遞到回呼函數物件
看完抽象的描述,我們最後來看實際的deviceone開發的例子,還是以do_Button為例子。
1. 當使用者點擊一個button,觸摸到的時候,系統會取得到button這個物件作為訊息來源,fire一個」touch」訊息,任何訂閱了」touch」訊息的函數物件都會接收到這個訊息並引起函數的執行。
//获取button对象 var btn_hello = ui("btn_hello"); //定义函数对象 function f(){ //当btn_hello这个按钮接收到手指点击就会执行下面的代码 deviceone.print("f 函数接收到点击触发消息") } function f(){ //当btn_hello这个按钮接收到手指点击就会执行下面的代码 deviceone.print("f 函数接收到点击触发消息") } //f,f订阅button的touch消息 btn_hello.on("touch",f); btn_hello.on("touch",f);
2. 我們可以為button物件定義2個自訂的訊息”message1」和”message2”,分別有2個函數物件訂閱這2個訊息。但是最後要觸發這個訊息必須是開發者透過呼叫fire函數才能觸發,這就是和系統訊息的差別。
//获取button对象 var btn_hello = ui("btn_hello"); //定义函数对象 function f(d){ //当btn_hello这个按钮接收到开发者触发的消息message就会执行下面的代码 deviceone.print("f 函数接收到message消息,消息的数据是:"+d) } function f(d){ //当btn_hello这个按钮接收到开发者触发的消息message就会执行下面的代码 deviceone.print("f 函数接收到message消息,消息的数据是:"+d) } //f,f订阅button的touch消息 btn_hello.on("message",f); btn_hello.on("message",f); //触发消息 btn_hello.fire("message","data"); btn_hello.fire("message","data");
看到这里,你肯定会奇怪,为什么我们要在button上自定义对象?这有神马意义?其实确实没有意义也没有必要,这里只是拿button举例子,在常规的开发中,基本不会这么用。
消息的使用
前面讲了这么多,现在才是deviceone消息的使用。使用其实很简单,上面的例子基本说明的了系统事件和自定义事件的使用方法。
有几个概念再说明一下
1.deviceone的所有对象,包括UI,MM,SM对象都可以是消息源
// SM对象可以是消息源 var page = sm("do_Page"); page.on("loaded",function()){ // 这个是page对象的系统消息,这个消息不需要手动触发,系统会自动触发 } page.on("message",function(d)){ // 这个是page对象的自定义消息 } page.fire("message","data"); // MM对象可以是消息源 var http = mm("do_Http"); http.on("result",function()){ // 这个是http对象的系统消息,这个消息不需要手动触发,接受到http服务端的反馈后会自动触发 } http.on("message",function(d)){ // 这个是http对象的自定义消息 } http.fire("message","data"); //UI对象可以是消息源 var alayout = ui("alayout_id"); alayout.on("touch",function()){ // 这个是alayout对象的系统消息,这个消息不需要手动触发,手机点击就会触发 } alayout.on("message",function(d)){ // 这个是alayout对象的自定义消息 } alayout.fire("message","data");
2.消息源对象有作用域,所以订阅和触发的消息源必须是是一个作用域的同一个对象。这里结合数据分享和数据传递文档来理解。
看以下的例子,test1.ui和test2.ui有可能在一个page作用域,也有可能不在一个作业域,只有在一个作用域fire的消息才能正确送达回调函数。
判断是否一样,可以通过打印page的地址 page.getAddress().
//在test.ui.js里订阅消息 var page = sm("do_Page"); deviceone.print(page.getAddress()); page.on("message",function(d)){ deviceone.print(d); } //在test.ui.js触发消息 var page = sm("do_Page"); deviceone.print(page.getAddress()); page.fire("message","data");
如果不在同一page作用域,则可以把消息订阅在2个page都能共享到的app作用域
上面的代码改成:
//在test.ui.js里订阅消息 var app = sm("do_App"); app.on("message",function(d)){ deviceone.print(d); } //在test.ui.js触发消息 var app = sm("do_App"); app.fire("message","data");
3.同样的函数对象可以重复订阅一个对象源的消息,触发消息的时候会使函数执行多次,这是初学者经常犯的错误。
var page = sm("do_Page"); var count = ; function f(){ deviceone.print("执行次数"+(count++)); } page.on("message",f); page.on("message",f); page.fire("message");
看上面的例子,如果执行的话,会打印2此,因为订阅了2次,或许你会说谁会写这样的代码?实际情况肯定没有这么容易看出来执行了重复的on函数,实际情况经常是比如在点击事件里执行on函数,每点击一下按钮,就重复订阅一次。
4.消息的订阅一定要在消息的触发之前,这是初学者经常犯的错误。
var page = sm("do_Page"); var count = ; function f(){ deviceone.print("执行次数"+(count++)); } page.fire("message"); page.on("message",f);
看上面的例子,如果执行的话,会没有效果,或许你会说谁会写这样的代码?实际情况肯定没有这么容易看出来顺序反了,实际情况经常是比如on函数执行在某一个函数的回调函数里,你无法确定回调函数啥时候执行,是否是在fire之前执行。一般碰到这种情况可以加几个deviceone.print打印一下看看是on先执行还是fire先执行。
5.有订阅就有取消订阅,取消订阅是off函数,之所以很少用,是因为closePage的时候会自动把当前page作用域订阅的消息全部释放。
但是如果消息订阅在app作用域,就要注意,可能需要手动去取消订阅。否则就会出现触发消息的时候会使函数执行多次的问题。
var page = sm("do_Page"); var count = ; function f(){ deviceone.print("执行次数"+(count++)); } page.on("message",f); page.fire("message"); .page.off("message"); page.fire("message");
看上面的例子,打印只会执行一次,因为fire一次后就取消订阅了。