作者:Truly
日期:2007.7.31
上次我們討論了Web開發中最重要的設計模式MVC,今天我們要討論的是Observer模式,與MVC這樣的大型設計模式相比,Observer模式則要輕量很多。廢話不多說了,進入主題
Obsever簡單應用
請先看一段代碼:
// the process array calling after page loaded for page listener.
var PageLoadListener = new Array();
// page listener
function onDocumentLoaded()
{
for (var a in PageLoadListener)
{
if(typeof PageLoadListener[a] == 'function')
PageLoadListener[a]();
}
}
// Add a listener to current page to run all function on the page.
if (document.addEventListener)
document.addEventListener('DOMContentLoaded', onDocumentLoaded, false);
else
window.attachEvent('onload', onDocumentLoaded);
而在另外一個js中我們定義:
PageLoadListener.push(domLoaded); // push the domLoaded function into the listener array.
// a method need to call after page is loaded
function domLoaded()
{
alert('document loaded');
}
通常我們經常要處理window.onload事件,例如使用下面代碼來指定onload事件
window.onload=aFunction
而當我們這樣的方式聲明的時候,很可能會覆蓋已經定義過的window.onload事件,或者我們這裡還有很多事件要在onload執行,那麼如何應對這種情況呢?Observer模式恰好可以用來處理這種情況。首先我們需要為頁面定義了一個監聽器,檢測頁面中需要處理的事件,然後定義一個全域的監聽器數組。這樣需要處理的事件都可以註冊到這個監聽器數組中,然後統一進行調用。
如上面代碼中的,我們將需要處理的事件名通過下面代碼
PageLoadListener.push(domLoaded);
註冊到Listener數組中,這樣的註冊過程可能遍布到不同的js檔案或指令碼塊中,最後使用監聽器集中對數組中的元素進行調用,這樣以來就很好的解決了window.onload事件衝突的問題。
Obsever進階應用
下面我們示範一個更加複雜的Obsever模式應用,來自著名的Prototype架構,請先查看代碼:
Hello.htm
<html>
<head>
<title>Obsever Demo</title>
<script language="javascript" type="text/javascript" src="Obsever.js"></script>
<script language="javascript" type="text/javascript" src="Controller.js"></script>
</head>
<body>
<input id='textbox1' name='textbox1'/>
<select id='selElement1' >
<option >choose</option>
<option value='1' >1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
</body>
</html>
Obsever.js
function $(id){return document.getElementById(id);}
var $A = Array.from = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) {
return iterable.toArray();
} else {
var results = [];
for (var i = 0, length = iterable.length; i < length; i++)
results.push(iterable[i]);
return results;
}
}
var Browser={
isWebKit : navigator.userAgent.indexOf('AppleWebKit/') > -1
}
Function.prototype.bind = function() {
var __method = this, args = $A(arguments), object = args.shift();
return function() {
return __method.apply(object, args.concat($A(arguments)));
}
}
if (!window.Event) {
var Event = new Object();
}
Object.extend = function(destination, source) {
for (var property in source) {
destination[property] = source[property];
}
return destination;
}
Object.extend(Event,
{
observe: function(element, name, observer, useCapture) {
if(typeof element != 'object')
element = $(element);
useCapture = useCapture || false;
if (name == 'keypress' &&
(isWebKit || element.attachEvent))
name = 'keydown';
Event._observeAndCache(element, name, observer, useCapture);
},
stopObserving : function(element, name, observer, useCapture) {
element = $(element);
useCapture = useCapture || false;
if (name == 'keypress' &&
(Browser.WebKit || element.attachEvent))
name = 'keydown';
if (element.removeEventListener) {
element.removeEventListener(name, observer, useCapture);
} else if (element.detachEvent) {
try {
element.detachEvent('on' + name, observer);
} catch (e) {}
}
},
observers: false,
_observeAndCache: function(element, name, observer, useCapture) {
if (!this.observers) this.observers = [];
if (element.addEventListener) {
this.observers.push([element, name, observer, useCapture]);
element.addEventListener(name, observer, useCapture);
} else if (element.attachEvent) {
this.observers.push([element, name, observer, useCapture]);
element.attachEvent('on' + name, observer);
}
}
}
)
Controller.js
function changeHandler()
{
$('textbox1').value=this.value;
}
Event.observe(window, 'load',
function(){Event.observe('selElement1','change',changeHandler.bind($('selElement1')));}
);
上面代碼示範了一個當選擇下拉框的時候,調整文字框的值。我們示範了onchange和onload事件的監聽,同樣的也可以應用到任何DOM節點的各個事件上。但是,你可能說你可以在<select>標籤中直接添加onchange事件就可以了,為什麼要這麼做?
Well,首先這樣可以更好的分離代碼和視圖,就像我上篇文章中討論的MVC模式,我們應該儘可能的分離代碼和視圖。尤其是當你構建一個大型的應用程式的時候,例如飛鴿這樣的網站,越是可以從中受益。
同時通過這種方式,可以設計出一個完整的用戶端事件流程。關於JavaScript事件模型的討論,將是我們後面文章的討論內容。
註:文中代碼部分取自著名的Prototype架構,不過根據行文需要,我做了適當改動 :)