插件化js开发
来源:广州中睿信息技术有限公司官网
发布时间:2012/10/21 23:25:16 编辑:editor 阅读 507
前两篇js自定义消息机制研究学习(一)&mdash&mdash看百度搜索输入提示js自定义消息机制研究学习(二)&mdash&mdash做一些改动,定制自己的消息机制研究了一些基础的自定义消息机制,

 

前两篇

js自定义消息机制研究学习(一) ——看百度搜索输入提示

js自定义消息机制研究学习(二)——做一些改动,定制自己的消息机制

研究了一些基础的自定义消息机制,对一些简单的开发已经足够。

现在我们来尝试面对一些稍微复杂一些的架构设计。

首先,增加了一个插件模式:

plugs
  var plugs=(function(){       function addPlugs(name,plug)       {               var __plugs=this.__plugs=this.__plugs || {};            if(name && plug)            {                 __plugs[name]={                    installed:false,                    instance:plug                    };            }                  }              function installPlugs()       {            var plugs=this.__plugs=this.__plugs || {};            for(var i in plugs)            {                var plug=plugs[i];                if(!plug.installed)                {                    plug.instance.install(this);                    plug.installed=true;                }            }       }              return {          ini:function(X)          {               X=monitor.ini(X);               if(X.live)               {                   var proto=X.prototype;                   proto.addPlugs=addPlugs;                   proto.installPlugs=installPlugs;               }                 X.__plugs={};               X.addPlugs=addPlugs;               X.installPlugs=installPlugs;                               }       }  })();

 

如果你看过前两篇代码,这段代码是比较容易看懂的。它为传入的对象(或函数)首先绑定了monitor模式,然后为对象增加了两个方法:addPlugs,installPlugs

addPlugs 添加插件

installPlugs 安装插件

添加插件很简单,只是把实体装到一个{}键值对中

安装插件,就会调用每个插件对象的:install方法



 

为什么要有插件模式呢?我们举个例子来说明

虚构一个需求:

1. 在页面上动画展示龟兔赛跑

2.兔子和乌龟可以说话

3.展示他们比赛过程

这里我没什么素材,只简单展示一下这个程序设计

第一步,我们构造一个Animal函数,作为兔子、乌龟的类,代码如下

Animal
      ///动物  function Animal(config)  {      config=config || {};      var othis=this;      this.name=config["name"] || "Anonymous";      this.x=config["x"] || 0;      var toward=1;//右:1,左-1      var __timeout=0;      var __speed=5;      //      this.say=function(str)      {          this.trigger({type:"say",msg:str});          return str;      }      //      this.stop=function()      {          clearTimeout(__timeout);           this.trigger({type:"stop",x:this.x});          return this.x;      }      //跑动      this.run=function(speed)      {          __speed=speed || __speed;          this.x+=__speed*toward;          __timeout=setTimeout(function(){ othis.run();},100);          this.trigger({type:"run",x:this.x,toward:toward,speed:__speed});          return {x:this.x,toward:toward,speed:__speed};      }      //向左转      this.turnLeft=function()      {          toward=-1;          this.trigger({type:"turn",toward:toward});          return toward;      }      //向右转      this.turnRight=function()      {          toward=1;          this.trigger({type:"turn",toward:toward});          return toward;      }  }


 

我们现在有一个动物的类(Animal),它有一些行为:说(say),停(stop),跑(run),左转(turnLeft),右转(turnRight) 

因为我在代码里使用了trigger的方法,我们先使用上一篇讲到monitor,初始化一下:

  monitor.ini(Animal);



 

第二步 我们先写一个记录的功能,用来记录动物对象的言行,这里只记录say,stop,turn的消息,代码如下:

View Code
  ///记录器  function Logger()  {       this.dom=document.getElementById("log");  }    Logger.prototype={        log:function(str)        {            var time=new Date();            this.dom.innerHTML+="<br/>"+str+'<span style="color:gray">('+time.getHours()+":"+time.getMinutes()+":"+time.getSeconds()+")</span>";        },        handler:function(data,p){            switch(data.type)            {                case "say":                    this.log(p.name+"  说:"+data.msg);                    break;                case "stop":                    this.log('<span style="color:green">'+p.name+" 停在了"+data.x+'</span>');                    break;                case "turn":                    this.log(p.name+" 转向了"+(data.toward==1?"":""));                    break;            }                }   }

我们这里用id=log的一个页面元素(div)来显示动物的言行

第三步,我们来创建兔子与乌龟这两个对象:

  var hare=new Animal({name:"兔子"});  var tortoise=new Animal({name:"乌龟"});



 

第四步,我们将记录器绑定到两个对象上

如果你要绑定到所有动物对象上,你可以使用上一篇讲到的live方法。在这里为了说明为什么要用插件,我们限定一下需求,假设live是不允许使用的,因为我们还要创建其他的一些动物,Logger是不需要记录这些非主角的行为。那么我们要编写如下的代码:

View Code
  var log=new Logger();   hare.bind("say",log);  hare.bind("stop",log);  hare.bind("turn",log);  tortoise.bind("say",log);  tortoise.bind("stop",log);  tortoise.bind("turn",log);  



 

现在提出一个问题,你如果仔细看的话,我们发现在介于live和bind之间,比如有100个Animal对象,我们只要求记录50个Animal的行为,那么我们该有多郁闷,我们需要写很多的bind,假如我们Animal不只这几个行为,我们为它扩展了100个行为,都要做记录,如果一行行的写bind,该多恐怖

好吧,改进一下,你或许会建议要函数,例如这样:

View Code
  function bindLog(ani)  {        ani.bind("say",log);        ani.bind("stop",log);        ani.bind("turn",log);  }  bindLog(hare);  bindLog(tortoise);

如果只有一两个这样的需求,这样也是不错的。那么再扩展呢?要写很多个,比如十个,类似于Logger的类,他们要去绑定100个Animal一个,数个,或者全部(全部当然可以考虑live)

假设这个十个类,有些需要每一个Animal对象都绑定单独的对象,比如我们写一个类Display,它来代表显示在屏幕上的动物,那么一个动物对象就需要对应一个Display

很快我们就会发现,我们需要写很多的类似于bindLog的方法。如bindDisplay,bindRun等等充斥在我们的主代码中

那么用开篇写插件,我们会怎么做?

首先把monitor.ini(Animal);修改为:plugs.ini(Animal)让它支持插件模式,因为插件模式默认绑定了monitor,所以我们不需要再初始化一次monitor




然后我们在Logger的prototype中,添加方法install



 

View Code
  install:function(main){              main.bind("say",this);              main.bind("stop",this);              main.bind("turn",this);  }



 

我们的绑定代码,修改成如下:

View Code
  //添加安装插件  var log=new Logger();   hare.addPlugs("log",log);  tortoise.addPlugs("log",log);  hare.installPlugs();  tortoise.installPlugs();

我们把一系列的bind,修改成为先使用addPlugs,最后调用installPlugs来完成。

这样做的优势?

你的主代码很清晰,完整如下:

View Code
  var hare=new Animal({name:"兔子"});  var tortoise=new Animal({name:"乌龟"});  //记录器  var log=new Logger();     hare.addPlugs("log",log);  tortoise.addPlugs("log",log);  hare.installPlugs();  tortoise.installPlugs();

这样,你很快就能看明白对象与对象之间的关系,也能从更高一点抽象层次上去组织你的对象,组织你的代码,而不需要头疼写一堆一堆的bind了,如果你要换掉一个Logger,比如换成一个只记录say的Logger 或者说你要把动物说的话以气泡的形式显示在他们的头顶。

当我们有很多的组件拼装时,这样模式很给我们带来很大的便利。

更重要的:

Animal 可以看做一个业务逻辑,我们很好的将它与显示进行了分离,在测试中,我们可以很简单的调用hare.run hare.say,而完全不用关心他们的显示表现,这样可以让我们在更高的抽象层次构建我们的js程序。对于一些复杂的业务,既可以让我们专注于业务,也可以让我们彼此分工合作,各写一块。



 

PS:

也许有人要问,为什么要addPlugs,然后再installPlugs,而不是一个addPlugs直接搞定。

在例子写完以后我发现了这个问题,这是我的疏忽,惯性思维让我沿用了我的实际使用的plugs。

其实我应用的plugs,还有一些扩展方法,如getPlug等等,在实际开发中插件的install方法后还会触发trigger一个消息等等

这样做的目的是,在installPlugs时,部分插件的安装,是需要知道其他插件的存在

还有一个原因,是涉及懒加载的,在addPlugs时,有时我会addPlugs一个配置,或者一个简称,然后有他们实际指向的js文件此时并未加载

直到installPlugs时,它才会真正加载js并创建plug对象。所以我是在插件配置都准备好的情况下,才请求相应的js文件(比如直接请求一个服务器合并多个js后的压缩文件)

希望不要给大家造成困扰。

我的建议是,如果你对这样的编程方式感兴趣,使用monitor,plugs(当然,他们也可以直接用来解决一些问题)之前最好根据自己的情况修改一下

 

完整代码示例:下载

在完整代码示例中,我增加了一个Display用于显示动物,然后抱着玩的心态加了段简单的故事脚本,博君一笑!

 

预告:

下一篇,是一篇关于消息机制的完结篇。会写一些杂七杂八的东西。

 

ozdoo技术系列文章
联系我们CONTACT 扫一扫
愿景:成为最专业的软件研发服务领航者
中睿信息技术有限公司 广州•深圳 Tel:020-38931912 务实 Pragmatic
广州:广州市天河区翰景路1号金星大厦18层中睿信息 Fax:020-38931912 专业 Professional
深圳:深圳市福田区车公庙有色金属大厦509~510 Tel:0755-25855012 诚信 Integrity
所有权声明:PMI, PMP, Project Management Professional, PMI-ACP, PMI-PBA和PMBOK是项目管理协会(Project Management Institute, Inc.)的注册标志。
版权所有:广州中睿信息技术有限公司 粤ICP备13082838号-2