1. 程式人生 > >js如何實現觀察者模式

js如何實現觀察者模式

定義:觀察者模式,又叫釋出訂閱者模式,又叫訊息系統,又叫訊息機制,又叫自定義事件,解決主體與觀察者之間的耦合問題

特點:

1 解決的是耦合問題(類與類之間,物件之間,類與物件之間,模組之間)

2 對於任何一個觀察者來說,其他觀察者的改變不會影響自身

3 對於任何一個物件來說,既可以是觀察者,也可以是被觀察者

 jQuery中的觀察者模式

$.CallBacks()方法執行的結果得到一個觀察者物件:

觀察者物件有一個方法叫add,用來訂閱訊息的

觀察物件有一個方法叫fire,用來發布訊息

// 定義一個觀察者物件
var Observer = $.Callbacks();
// 訂閱一個訊息,訂閱today訊息,當接收該訊息的時候,我們釋出今題的日期
Observer.add('today', function () {
	console.log(arguments)
})
document.onclick = function () {
	Observer.fire('today', 'hello', 'word', 'success')
}

得到結果如下:

 

 js中觀察者模式的實現

觀者者物件必須具備兩個方法

regist 用來註冊訊息

第一個引數表示訊息的名稱

第二個引數表示回撥函式

fire 用來觸發訊息

第一個引數表示訊息的名稱

第二個引數表示傳遞資料

實現方法如下:

// 定義觀察者物件
var Observer = (function () {
	// 通過閉包,儲存的回撥函式對使用者來說是不可見的
	var __message = {};
	// 我們希望resit和fire是可以訪問的
	return {
		/**
		 * 用來註冊訊息
		 * @type 	訊息的名稱
		 * @fn 		回撥函式	
		 **/
		regist: function (type, fn) {
			// 將回調函式註冊到__message變數中
			// 對於message訊息管道來說,我們要根據type開闢不同的儲存空間,來儲存fn(放在陣列中)
			// 判斷有沒有type型別的儲存空間
			if (__message[type]) {
				// 儲存回撥函式
				__message[type].push(fn);
			} else {
				// 開闢新的儲存空間儲存回撥函式
				__message[type] = [fn];
			}
		},
		/**
		 * 用來觸發訊息
		 * @type 	訊息的名稱
		 * @data 	傳遞的資料
		 **/
		fire: function (type, obj) {
			// 適配引數
			var params = {
				// 這裡做了優化,可以傳入作用域和引數,作用域要傳入一個物件,引數要傳入一個數組
				context: (obj && obj.context) || null,
				args: (obj && obj.args) || []
			}
            //把type傳入函式引數中,放在最前邊,作為第一個引數
			params.args.unshift(type)
			// 在訊息管道中,尋找有沒有該型別的訊息,所以遍歷訊息管道
			if (__message[type]) {
				// __message[i] 是一個數組,遍歷這個陣列並執行
				for (var i = 0; i < __message[type].length; i++) {
					// 我們要將自定義資料傳遞進來
					// __message[type][i](data)
					// 我們想實現jqyery的Callbacks觸發方式
					// call 將引數意義列舉進來,apply將引數作為陣列傳遞
					__message[type][i].apply(params.context, params.args)
				}
			}
		},
		// 有時候我們也希望登出事件
		/**
		 * 登出事件
		 * @type 	訊息型別
		 * @fn 		訊息回撥函式
		 **/
		remove: function (type, fn) {
			// 將fn從type的訊息佇列中刪除
			if (__message[type]) {
				// 判斷該佇列中是否擁有fn
				// 從陣列中刪除一個成員用什麼方法?
				// 從前遍歷還是從後遍歷?
				// 我們從後向前遍歷,因為刪除的某一項只會影響該項後面的成員,前面的成員索引值不會影響,所以可以正常遍歷
				for (var i = __message[type].length - 1; i >= 0; i--) {
					// 判斷該項是否是fn
					if (__message[type][i] === fn) {
						// 刪除這一項
						__message[type].splice(i, 1);
					}
				}
			}
		}
	}
})()

 使用封裝的觀察者方法如下:

var hh = function () {
	console.log(arguments, this)
}
// 訂閱訊息
Observer.regist('say', hh)
//定義作用域
var obj = {
	title: 'mm'
}
//觸發訊息
Observer.fire('say', {
	// 傳遞一個作用域,希望我們的回撥函式在obj作用域下執行
	context: obj,
	args: ['a','b']
})
/*
* 如果要登出哪個訊息裡的函式,就把哪個訊息和它對應的函式傳入進去,進行登出
* //登出這個say函式
* Observer.remove('say', hh)
*/

 得到結果如下: