Javascript组合模式分析与实例

Javascript组合模式是一种专为创建web上的动态用户界面而量身定制的模式。使用这种模式,可以用一条命令在多个对象上激发复杂的或递归的行为。这可以简化粘合性代码,使其更容易维护,而那些复杂行为则被委托给各个对象。

组合模式的好处

使用组合模式,简单的操作也能产生复杂的结果。你不必编写大量手工遍历数组或其他数据结构的粘合代码,只需对最顶层的对象执行操作,让每一个字对象自己传递这个操作即可。这对再三执行的操作尤其有用。

组合模式的弊端

组合对象的易用性可能掩盖了它所支持的每一种操作的代价。由于对组合对象调用的任何操作都会被传递到它的所有子对象,如果这个层次体系很大的话,系统的性能将会受到影响。

实例

贴上源码,分为三部分,也可以点击此Demo预览

第一部分 定义接口类 Interface

/* 定义接口类 */
var Interface = function(name,methods){
  
  if(arguments.length != 2) {
    throw new Error('接口参数必须是两个');
  }

  if(Object.prototype.toString.call(methods) != '[object Array]') {
    throw new Error('第二个参数必须是数组');
  }

  this.name = name;
  this.methods = [];


  for(var i=0,len=methods.length; i<len; i++) {
    if(typeof methods[i] !== 'string') {
      throw new Error('第二个参数的每一项的类型必须为字符串');
    }
    this.methods.push(methods[i]);
  }

}

Interface.ensureImplements  = function(obj) {
  
  if(arguments.length<2) {
    throw new Error('接口参数不能少于两个');
  }
  
  for(var i=1,len=arguments.length; i<len; i++) {
  
    var interface = arguments[i];
  
    if(interface.constructor !== Interface) {
      throw new Error('第二个参数开始的所有参数都必须为类Inerface的实例');
    }
  
    for(var j=0, methodsLen = interface.methods.length; j<methodsLen; j++) {
      var method = interface.methods[j];
      if(!obj[method] || typeof obj[method] !== 'function') {
        throw new Error('接口不匹配或类型不对');
      }
    }
  }
  
}

第二部分 创建接口对象

/* 创建组合对象和叶对象需要实现的接口 */
var composite = new Interface('Composite',['add','remove','getChild']);
var listItem = new Interface('ListItem',['expand','collapse']);

第三部分 定义组合类和叶类,并实例化

/* 定义总的组合类 CompositeList */
var CompositeList = function(id) {
  this.children = [];
  this.element = document.createElement('ul');
  this.element.id = id;
}

CompositeList.prototype = {
  
  add: function(child){
    this.children.push(child);
    this.element.appendChild(child.getElement());
  },
  
  remove: function(child){
    for(var i=0,len = this.children.length; i<len; i++) {
      if(this.children[i] == child) {
        this.children.splice(i,1);
        this.element.removeChild(child.getElement());
      }
      break;
    }
  },
  
  expand: function(){
    for(var i=0,len = this.children.length; i<len; i++) {
      this.children[i].expand();
    }
  },
  
  collapse: function(){
    for(var i=0; i<this.children.length; i++) {
      this.children[i].collapse();
    }
  },
  
  getChild: function(i){
    return this.children[i];
  },
  
  getElement: function(){
    return this.element;
  }
}

/* 定义层次体系中的组合类 PartList */
var PartList = function() {
  Interface.ensureImplements(this,composite,listItem); //接口验证,由于PartList继承CompositeList.prototype方法,所以在此验证即可
  this.children = [];
  this.element = document.createDocumentFragment();
}

/* 类式继承 */
PartList.prototype =  new CompositeList();

/* 定义叶类 */
var Item = function(text){
  Interface.ensureImplements(this,listItem); //接口验证
  this.element = document.createElement('li');
  this.html = document.createTextNode(text);
  this.element.appendChild(this.html)
  this.element.className = 'hidden'
}

Item.prototype = {
  expand: function(){
    this.element.className = 'show'
  },
  
  collapse: function(){
    this.element.className = 'hidden';
  },
  
  getElement: function(){
    return this.element;
  }
}

var allList = new CompositeList('list');
var partList = new PartList();

var a = new Item('a');
var b = new Item('b');
var c = new Item('c');
var d = new Item('d');

allList.add(a);
partList.add(b);
partList.add(c);
allList.add(partList);
allList.add(d);

document.body.appendChild(allList.getElement());

之所以写上面的代码,是源于所在公司开发的一套分布式文件系统,这套系统编码部分现已完成,其中一个栏目的js与php部分是我写的,运用了Ajax技术多次获取大量的数据写入dom节点中(由于这套系统用在局域网中,所以很多操作都在一个页面中完成,以至于获得更好的用户体验,同时也省了 new Image()),为了方便浏览,不得不在前端加入js分页,其中显示的内容分为单个显示、多个显示、全部显示,假如用到组合模式,那简直是天仙配,可惜的是当时比较弱智,没想到,以至于成了事后诸葛亮,将其写在博客中给自己提个醒!

写完之后,我一直在担心IE6内存泄露的问题,貌似有段日子没有用IE6了,为了IE6,再虚拟出一个xp来,多少有点不值得,还是去做更重要的事吧!

下面附上IE6存在的内存泄露问题

Cross-Page Leaks

大家可以看看这段例子代码:

<html> 
<head> 
<script>
function  LeakMemory() { // 这个函数会引发Cross-Page Leaks 

  var  hostElement  =  document.getElementById( " hostElement " );

  /* Do it a lot, look at Task Manager for memory response */
  for (i  =   0 ; i  <   5000 ; i ++ ) {
    var parentDiv  = document.createElement('<div onclick="foo()">');
    var childDiv = document.createElement(' <div onclick="foo()">');
    /* This will leak a temporary object */
    parentDiv.appendChild(childDiv);
    hostElement.appendChild(parentDiv);
    hostElement.removeChild(parentDiv);
    parentDiv.removeChild(childDiv);
    parentDiv  =   null ;
    childDiv  =   null ;
  }

  hostElement  =   null ;
}

function  CleanMemory() {  // 而这个函数不会引发Cross-Page Leaks 

  var  hostElement  =  document.getElementById( " hostElement " );
  /* Do it a lot, look at Task Manager for memory response */
  for (i  =   0 ; i  <   5000 ; i ++ ) {
    var parentDiv  = document.createElement('<div onclick="foo()">');
    var childDiv = document.createElement(' <div onclick="foo()">');
    /* Changing the order is important, this won't leak */
    hostElement.appendChild(parentDiv);
    parentDiv.appendChild(childDiv);
    hostElement.removeChild(parentDiv);
    parentDiv.removeChild(childDiv);
    parentDiv  =   null ;
    childDiv  =   null ;
  }

  hostElement  =   null ;

}
</script> 
</head>
<body> 
  <button onclick = "LeakMemory()" > Memory Leaking Insert </button> 
  <button onclick = "CleanMemory()" > Clean Insert </button> 
  <div id = "hostElement" ></ div > 
</body> 
</html> 

LeakMemory和CleanMemory这两段函数的唯一区别就在于他们的代码的循序,从代码上看,两段代码的逻辑都没有错。

但LeakMemory却会造成泄露。原因是LeakMemory()会先建立起parentDiv和childDiv之间的连接,这时候,为了让 childDiv能够获知parentDiv的信息,因此IE需要先建立一个临时的scope对象。而后parentDiv建立了和 hostElement对象的联系,parentDiv和childDiv直接使用页面document的scope。可惜的是,IE不会释放刚才那个临时的scope对象的内存空间,直到我们跳转页面,这块空间才能被释放。而CleanMemory函数不同,他先把parentDiv和 hostElement建立联系,而后再把childDiv和parentDiv建立联系,这个过程不需要单独建立临时的scope,只要直接使用页面 document的scope就可以了, 所以也就不会造成内存泄露了

详细原因,可以看看Understanding and Solving Internet Explorer Leak Patterns这篇文章。

This entry was posted in Javascript/Ajax and tagged , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>