1. 觀察者模式介紹
觀察者模式也稱發(fā)布訂閱(publish/subscribe簡稱Pub/Sub)模式。這是一種創(chuàng)建松散耦合代碼的技術界逛。它定義對象間 一種一對多的依賴關系,當一個對象的狀態(tài)發(fā)生改變時际插,所有依賴于它的對象都將得到通知哥艇。由主體和觀察者組成禁谦,主體負責發(fā)布事件胁黑,同時觀察者通過訂閱這些事件來觀察該主體。主體并不知道觀察者的任何事情州泊,觀察者知道主體并能注冊事件的回調函數(shù)丧蘸。
使用觀察者模式的好處:
- 支持簡單的廣播通信,自動通知所有已經訂閱過的對象遥皂。
- 頁面載入后目標對象很容易與觀察者存在一種動態(tài)關聯(lián)力喷,增加了靈活性。
- 目標對象與觀察者之間的抽象耦合關系能夠單獨擴展以及重用演训。
應用場景:當一個對象的改變需要同時改變其它對象弟孟,并且它不知道具體有多少對象需要改變的時候,就應該考慮使用觀察者模式样悟。觀察者模式所做的工作就是在解耦拂募,讓耦合的雙方都依賴于抽象,而不是依賴于具體乌奇。從而使得各自的變化都不會影響到另一邊的變化没讲。
1.1 觀察者模式-demo1:一個簡單的觀察者模式代碼實現(xiàn)
觀察者模式中主要有三個基本操作:訂閱眯娱、發(fā)布和退訂
// 聲明主體對象礁苗,利用閉包隱藏event對象
var PubSub_1 = (function () {
var event = {};
return {
on: function (eventName, callback) { // 訂閱
event[eventName] = callback;
},
off: function (eventName) { // 退訂
event[eventName] && (delete event[eventName]);
},
emit: function (eventName) { // 發(fā)布
event[eventName] && event[eventName](arguments[1]);
}
}
}());
// 訂閱相關信息
PubSub_1.on("newspaper", function () {
arguments[0] ? console.log(arguments[0] + "新一期報紙出來了") : console.log("無名氏新一期報紙出來了");
});
PubSub_1.on("newspaper", function () {
arguments[0] ? console.log(arguments[0] + "新1期報紙出來了") : console.log("無名氏新1期報紙出來了");
});
PubSub_1.on("news", function () {
arguments[0] ? console.log(arguments[0] + "最新新聞出來了") : console.log("無名氏最新新聞出來了");
});
// 發(fā)布相關信息
PubSub_1.emit("newspaper", "bob"); // bob新1期報紙出來了
PubSub_1.emit("news", "bob"); // bob最新新聞出來了
PubSub_1.emit("newspaper"); // 無名氏新1期報紙出來了
PubSub_1.emit("news"); // 無名氏最新新聞出來了
// 退訂
PubSub_1.off("newspaper");
不足之處:一個事件只能綁定一個操作,并且取消訂閱把整個事件都刪除掉了
1.2 觀察者模式-demo2
用一個數(shù)組把一個事件綁定的多個操作存儲起來,發(fā)布時按訂閱順序執(zhí)行徙缴,退訂時刪除對應的數(shù)組元素
var PubSub_2 = (function () {
var event = {};
return {
on: function (eventName, callback) { // 訂閱
!event[eventName] && (event[eventName] = []);
event[eventName].push(callback);
},
off: function (eventName, callback) { // 退訂
if (event[eventName]) {
event[eventName] = event[eventName].filter(function (value, index) {
return value != callback ? value : null;
});
}
},
emit: function (eventName, args) { // 發(fā)布
if (event[eventName]) {
for (let i = 0, len = event[eventName].length; i < len; i++) {
event[eventName][i](args);
}
}
}
}
}());
// 訂閱相關信息
function newspaper1() {
console.log("第1期報紙出來了");
}
function newspaper2() {
console.log("第2期報紙出來了");
}
function news() {
console.log("最新新聞出來了");
}
PubSub_2.on("newspaper", newspaper1);
PubSub_2.on("newspaper", newspaper2);
PubSub_2.on("news", news);
// 發(fā)布相關信息
PubSub_2.emit("newspaper"); // 第1期報紙出來了试伙、第2期報紙出來了
PubSub_2.emit("news"); // 最新新聞出來了
// 退訂
PubSub_2.off("newspaper", newspaper1);
PubSub_2.emit("newspaper"); // 第2期報紙出來了
相關問題:我在測試代碼中沒有給訂閱方法寫參數(shù),如果需要參數(shù)可以參考demo1于样,也可以自由發(fā)揮呀~~
1.3 觀察者模式-jquery中的觀察者模式
作為一個前端開發(fā)者疏叨,我們經常與jquery對象打交道,常用的jquery對象方法on穿剖、off蚤蔓、trigger(bind/unbind類似)就可以看做觀察者模式中的訂閱、退訂和發(fā)布操作糊余。
// 聲明事件處理函數(shù)
function jq_newspaper1(event, msg1, msg2) {
// event是jquery事件處理函數(shù)自帶的參數(shù)
// event.data是綁定事件處理函數(shù)時傳入的參數(shù)
// 除去event的其它參數(shù)msg1,msg2是與trigger觸發(fā)事件時傳入的參數(shù)數(shù)組中的每一項對應的
console.log(event.data[0] + "jquery_第1期報紙出來了" + (msg1 || "") + (msg2 || ""));
}
function jq_newspaper2() {
console.log("jquery_第2期報紙出來了");
}
$(document).on("newspaper.test", ["hello1~"], jq_newspaper1); // .test是namespace event.data = ["hello1~"] 是對象
$(document).on("newspaper", jq_newspaper2);
$(document).trigger("newspaper.test", ["msg11", "msg22"]); // hello1~jquery_第1期報紙出來了msg11msg22
$(document).trigger("newspaper"); // hello1~jquery_第1期報紙出來了秀又、jquery_第2期報紙出來了
$(document).off("newspaper.test"); // 解綁newspaper.test事件 等價于 $(document).off(".test")
$(document).trigger("newspaper"); // jquery_第2期報紙出來了
需要注意的點:
- on、off贬芥、trigger是jquery實例的方法吐辙,所以要先實例化一個jquery對象才可以使用這些方法;
- jquery實現(xiàn)的觀察者模式蘸劈,比較靈活昏苏。其中最大的好處是命名空間的使用,命名空間最方便的就是在于可以觸發(fā)和解綁對應的事件而不會影響其它同一類型的事件;
- jquery事件處理函數(shù)中默認的第一個形參是event贤惯,該對象包含發(fā)生該事件時的一些基本屬性洼专,其中event.data是利用on方法綁定事件時傳入的一個對象(可選);
- jquery事件處理函數(shù)中除第一個參數(shù)的其它形參是調用trigger方法的時傳入一個實參數(shù)組(數(shù)組中的每一項分別和事件處理函數(shù)第二個形參開始的形參相對應)孵构。
1.4 自定義jquery版觀察者模式
使用方式類似于jquery實例對象方法on壶熏、off、trigger浦译,只是不用實例化一個jquery對象了棒假,但是自定義版的jquery構造函數(shù)的方法綁定瀏覽器事件是不行的,只有jquery實例對象方法on才可以綁定瀏覽器事件精盅。
(function ($) {
// 實例化一個jquery對象
var eventObj = $({});
$.on = function () {
// 具體為什么要用apply 看jquery源碼這一塊的操作吧
eventObj.on.apply(eventObj, arguments);
};
$.off = function () {
eventObj.off.apply(eventObj, arguments);
};
$.trigger = function () {
eventObj.trigger.apply(eventObj, arguments);
};
}(jQuery));
// 回調函數(shù)
function handle(event, name, welcome) {
console.log(name + "自定義jquery觀察者模式開始啦~" + welcome);
}
// 訂閱
$.on("info", handle);
// 發(fā)布
$.trigger("info", ["sunny1_", "welcome~"]); // sunny1_自定義jquery觀察者模式開始啦~welcome~
// 退訂
$.off("info");
說明:為了和jquery實例的那些方法一致帽哑,所以取得方法名也是一樣的,這樣是不好的叹俏。
1.5 觀察者模式-demo1變種
基于demo1實現(xiàn)的另一種觀察者模式
var PubSub_1_variation = {
on: function (eventName, callback) { // 訂閱
if (!this.event) {
this.event = {};
}
this.event[eventName] = callback;
},
off: function (eventName) { // 退訂
if (this.event && this.event[eventName]) {
delete this.event[eventName]
}
},
emit: function (eventName) { // 發(fā)布
if (this.event && this.event[eventName]) {
this.event[eventName](arguments[1]);
}
}
};
var person1 = {};
var person2 = {};
var person1_variation = {};
var person2_variation = {};
Object.assign(person1, PubSub_1);
Object.assign(person2, PubSub_1);
Object.assign(person1_variation, PubSub_1_variation);
Object.assign(person2_variation, PubSub_1_variation);
function call1() {
console.log(arguments[0] + "-call1");
}
function call2() {
console.log(arguments[0] + "-call2");
}
person1.on("call1", call1);
person2.on("call2", call2);
person1_variation.on("call1", call1);
person2_variation.on("call2", call2);
person1.emit("call1", "person1"); // person1-call1
person1.emit("call2", "person1"); // person1-call2
person2.emit("call1", "person2"); // person2-call1
person2.emit("call2", "person2"); // person2-call2
person1_variation.emit("call1", "person1_variation"); // person1_variation-call1
person1_variation.emit("call2", "person1_variation"); // 不輸出
person2_variation.emit("call1", "person2_variation"); // 不輸出
person2_variation.emit("call2", "person2_variation"); // person2_variation-call2
說明:demo1因為用的是閉包方式妻枕,PubSub_1共享同一個event對象,所以操作的event對象都是同一個粘驰。demo2也有類似的問題屡谐,解決辦法類似。最后蝌数,Object.assign(target, ...sources) 方法用于將所有可枚舉屬性的值從一個或多個源對象復制到目標對象愕掏。它將返回目標對象。如果屬性值是對象那么將共享同一個內存空間顶伞。
最后:文章內容僅是自己利用匱乏的知識寫的一些demo饵撑,如果有不對的地方,麻煩指正一下~~~謝謝啦唆貌。