Truly
写精彩代码 品暇逸人生
posts - 91,comments - 425,trackbacks - 55

作者: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框架,不过根据行文需要,我做了适当改动 :)
 

posted on 2007-07-31 21:34  Truly  阅读(3052)  评论(4编辑  收藏