设计模式在JavaScript中的应用(2) -- Observer

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

你可能感兴趣的:(JavaScript)