`
abruzzi
  • 浏览: 452745 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

JavaScript内核系列 第8章 面向对象的JavaScript(下)

阅读更多

接上篇:JavaScript内核系列 第8章 面向对象的JavaScript(上)

8.4实例:事件分发器

这一节,我们通过学习一个面向对象的实例来对JavaScript的面向对象进行更深入的理解,这个例子不能太复杂,涉及到的内容也不能仅仅为继承,多态等概念,如果那样,会失去阅读的乐趣,最好是在实例中穿插一些讲解,则可以得到最好的效果。

本节要分析的实例为一个事件分发器(Event Dispatcher),本身来自于一个实际项目,但同时又比较小巧,我对其代码做了部分修改,去掉了一些业务相关的部分。

事件分发器通常是跟UI联系在一起的,UI中有多个组件,它们之间经常需要互相通信,当UI比较复杂,而页面元素的组织又不够清晰的时候,事件的处理会非常麻烦。在本节的例子中,事件分发器为一个对象,UI组件发出事件到事件分发器,也可以注册自己到分发器,当自己关心的事件到达时,进行响应。如果你熟悉设计模式的话,会很快想到观察者模式,例子中的事件分发器正式使用了此模式。

 

var uikit = uikit || {};
uikit.event = uikit.event || {};
 
uikit.event.EventTypes = {
    EVENT_NONE : 0,
    EVENT_INDEX_CHANGE : 1,
    EVENT_LIST_DATA_READY : 2,
    EVENT_GRID_DATA_READY : 3
};

 

 

定义一个名称空间uikit,并声明一个静态的常量:EventTypes,此变量定义了目前系统所支持的事件类型。

 

 

uikit.event.JSEvent = Base.extend({
    constructor : function(obj){
       this.type = obj.type || uikit.event.EventTypes.EVENT_NONE;
       this.object = obj.data || {};
    },
   
    getType : function(){
       return this.type;
    },
   
    getObject : function(){
       return this.object;
    }
});

 

 

 

定义事件类,事件包括类型和事件中包含的数据,通常为事件发生的点上的一些信息,比如点击一个表格的某个单元格,可能需要将该单元格所在的行号和列号包装进事件的数据。

 

 

uikit.event.JSEventListener = Base.extend({
    constructor : function(listener){
       this.sense = listener.sense;
       this.handle = listener.handle || function(event){};
    },
   
    getSense : function(){
       return this.sense;
    }
});

 

 

 

定义事件监听器类,事件监听器包含两个属性,及监听器所关心的事件类型sense和当该类型的事件发生后要做的动作handle

 

 

uikit.event.JSEventDispatcher = function(){
    if(uikit.event.JSEventDispatcher.singlton){
       return uikit.event.JSEventDispatcher.singlton;
    }
 
    this.listeners = {};
 
    uikit.event.JSEventDispatcher.singlton = this;
 
    this.post = function(event){
       var handlers = this.listeners[event.getType()];
       for(var index in handlers){
           if(handlers[index].handle && typeof handlers[index].handle == "function")
           handlers[index].handle(event);
       }
    };
 
    this.addEventListener = function(listener){
       var item = listener.getSense();
       var listeners = this.listeners[item];
       if(listeners){
           this.listeners[item].push(listener);
       }else{
           var hList = new Array();
           hList.push(listener);
           this.listeners[item] = hList;  
       }
    };
}
 
uikit.event.JSEventDispatcher.getInstance = function(){
    return new uikit.event.JSEventDispatcher();  
};

 

 

 

这里定义了一个单例的事件分发器,同一个系统中的任何组件都可以向此实例注册自己,或者发送事件到此实例。事件分发器事实上需要为何这样一个数据结构:

 

var listeners = {
    eventType.foo : [
       {sense : "eventType.foo", handle : function(){doSomething();}}
       {sense : "eventType.foo", handle : function(){doSomething();}}
       {sense : "eventType.foo", handle : function(){doSomething();}}
    ],
    eventType.bar : [
       {sense : "eventType.bar", handle : function(){doSomething();}}
       {sense : "eventType.bar", handle : function(){doSomething();}}
       {sense : "eventType.bar", handle : function(){doSomething();}}
    ],..
};

 

 

当事件发生之后,分发器会找到该事件处理器的数组,然后依次调用监听器的handle方法进行相应。好了,到此为止,我们已经有了事件分发器的基本框架了,下来,我们开始实现我们的组件(Component)

         组件要通信,则需要加入事件支持,因此可以抽取出一个类:

 

uikit.component = uikit.component || {};
 
uikit.component.EventSupport = Base.extend({
  constructor : function(){
   
  },
 
  raiseEvent : function(eventdef){
       var e = new uikit.event.JSEvent(eventdef);
       uikit.event.JSEventDispatcher.getInstance().post(e);    
  },
 
  addActionListener : function(listenerdef){
       var l = new uikit.event.JSEventListener(listenerdef);
       uikit.event.JSEventDispatcher.getInstance().addEventListener(l);
  }
});

 

 

 

继承了这个类的类具有事件支持的能力,可以raise事件,也可以注册监听器,这个EventSupport仅仅做了一个代理,将实际的工作代理到事件分发器上。

 

 

uikit.component.ComponentBase = uikit.component.EventSupport.extend({
  constructor: function(canvas) {
       this.canvas = canvas;
  },
 
  render : function(datamodel){}
});

 

 

 

定义所有的组件的基类,一般而言,组件需要有一个画布(canvas)的属性,而且组件需要有展现自己的能力,因此需要实现render方法来画出自己来。

 

我们来看一个继承了ComponentBase的类JSList

 

 

uikit.component.JSList = uikit.component.ComponentBase.extend({
    constructor : function(canvas, datamodel){
       this.base(canvas);
       this.render(datamodel);
    },
   
    render : function(datamodel){
       var jqo = $(this.canvas);
       var text = "";
       for(var p in datamodel.items){
           text += datamodel.items[p] + ";";
       }
       var item = $("<div></div>").addClass("component");
       item.text(text);
       item.click(function(){
           jqo.find("div.selected").removeClass("selected");
           $(this).addClass("selected");
          
           var idx = jqo.find("div").index($(".selected")[0]);
           var c = new uikit.component.ComponentBase(null);
           c.raiseEvent({
              type : uikit.event.EventTypes.EVENT_INDEX_CHANGE,
              data : {index : idx}
           });
       });
      
       jqo.append(item);
    },
   
    update : function(event){
       var jqo = $(this.canvas);
       jqo.empty();
       var dm = event.getObject().items;
 
       for(var i = 0; i < dm.length();i++){
           var entity = dm.get(i).item;
           jqo.append(this.createItem({items : entity}));
       }
    },
   
    createItem : function(datamodel){
       var jqo = $(this.canvas);
       var text = datamodel.items;
 
       var item = $("<div></div>").addClass("component");
       item.text(text);
       item.click(function(){
           jqo.find("div.selected").removeClass("selected");
           $(this).addClass("selected");
          
           var idx = jqo.find("div").index($(".selected")[0]);
           var c = new uikit.component.ComponentBase(null);
           c.raiseEvent({
              type : uikit.event.EventTypes.EVENT_INDEX_CHANGE,
              data : {index : idx}
           });
       });
      
       return item;
    },
   
    getSelectedItemIndex : function(){
       var jqo = $(this.canvas);
       var index = jqo.find("div").index($(".selected")[0]);
       return index;
    }
});

 

 

 

首先,我们的画布其实是一个共jQuery选择的选择器,选择到这个画布之后,通过jQuery则可以比较容易的在画布上绘制组件。

 

在我们的实现中,数据与视图是分离的,我们通过定义这样的数据结构:

 

 

{items : ["China", "Canada", "U.S.A", "U.K", "Uruguay"]};

 

 

 

则可以render出如下图所示的List

 

 

好,既然组件模型已经有了,事件分发器的框架也有了,相信你已经迫不及待的想要看看这些代码可以干点什么了吧,再耐心一下,我们还要写一点代码:

 

 

$(document).ready(function(){
    var ldmap = new uikit.component.ArrayLike(dataModel);
   
    ldmap.addActionListener({
       sense : uikit.event.EventTypes.EVENT_INDEX_CHANGE,
       handle : function(event){
           var idx = event.getObject().index;
           uikit.component.EventGenerator.raiseEvent({
              type : uikit.event.EventTypes.EVENT_GRID_DATA_READY,
              data : {rows : ldmap.get(idx).grid}
           });
       }
    });
   
    var list = new uikit.component.JSList("div#componentList", []);
    var grid = new uikit.component.JSGrid("div#conditionsTable table tbody");
   
    list.addActionListener({
        sense :  uikit.event.EventTypes.EVENT_LIST_DATA_READY,
        handle : function(event){
            list.update(event);
        }
    });
 
    grid.addActionListener({
       sense : uikit.event.EventTypes.EVENT_GRID_DATA_READY,
       handle : function(event){
           grid.update(event);
       }
    });
 
    uikit.component.EventGenerator.raiseEvent({
       type : uikit.event.EventTypes.EVENT_LIST_DATA_READY,
       data : {items : ldmap}
    });
 
    var colorPanel = new uikit.component.Panel("div#colorPanel");
    colorPanel.addActionListener({
       sense : uikit.event.EventTypes.EVENT_INDEX_CHANGE,
       handle : function(event){
           var idx = parseInt(10*Math.random())
           colorPanel.update(idx);
       }
    });
});
 

 

 

使用jQuery,我们在文档加载完毕之后,新建了两个对象ListGrid,通过点击List上的条目,如果这些条目在List的模型上索引发生变化,则会发出EVENT_INDEX_CHAGE事件,接收到这个事件的组件或者DataModel会做出相应的响应。在本例中,ldmap在接收到EVENT_INDEX_CHANGE事件后,会组织数据,并发出EVENT_GRID_DATA_READY事件,而Grid接收到这个事件后,根据事件对象上绑定的数据模型来更新自己的UI

上例中的类继承关系如下图:


事件分发器类层次

 

         应该注意的是,在绑定完监听器之后,我们手动的触发了EVENT_LIST_DATA_READY事件,来通知List可以绘制自身了:

 

 

    uikit.component.EventGenerator.raiseEvent({
       type : uikit.event.EventTypes.EVENT_LIST_DATA_READY,
       data : {items : ldmap}
    });

 

 

 

在实际的应用中,这个事件可能是用户在页面上点击一个按钮,或者一个Ajax请求的返回,等等,一旦事件监听器注册完毕,程序就已经就绪,等待异步事件并响应。

 

点击List中的元素ChinaGrid中的数据发生变化


点击CanadaGrid中的数据同样发生相应的变化:


 

由于ListGrid的数据是关联在一起的,他们的数据结构具有下列的结构:

 

var dataModel = [{
    item: "China",
    grid: [
        [{
            dname: "Beijing",
            type: "string"
        },
        {
            dname: "ProductA",
            type: "string"
        },
        {
            dname: 1000,
            type: "number"
        }],
        [{
            dname: "ShangHai",
            type: "string"
        },
        {
            dname: "ProductB",
            type: "string"
        },
        {
            dname: 23451,
            type: "number"
        }],
        [{
            dname: "GuangZhou",
            type: "string"
        },
        {
            dname: "ProductB",
            type: "string"
        },
        {
            dname: 87652,
            type: "number"
        }]
    ]
},...
];

 

 

一个组件可以发出多种事件,同时也可以监听多种事件,所以我们可以为List的下标改变事件注册另一个监听器,监听器为一个简单组件Panel,当接收到这个事件后,该Panel会根据一个随机的颜色来重置自身的背景色(注意在ListGrid下面的灰色Panel)



 附:由于作者本身水平有限,文中难免有纰漏错误等,或者语言本身有不妥当之处,欢迎及时 指正,提出建议,参与讨论,谢谢大家!

  • 大小: 1.6 KB
  • 大小: 5.4 KB
  • 大小: 5.6 KB
  • 大小: 6.4 KB
  • 大小: 14.5 KB
分享到:
评论
21 楼 t42dw 2010-06-12  
还没看完,先支持下!!
20 楼 icybamboo 2010-05-25  
很好的文章,看后很受启发,支持一下lz
19 楼 yuyanshan 2010-05-22  
abruzzi 写道
hi,guys
源码已经上传了,有兴趣的可以结合文章一起参考。


太好了,这下不用我再花时间自己去写没有的那部分了。嘿嘿。3Q
18 楼 abruzzi 2010-05-20  
hi,guys
源码已经上传了,有兴趣的可以结合文章一起参考。
17 楼 abruzzi 2010-05-18  
yuyanshan 写道
建议楼主附上例子的源代码,看了蛮久是有些困难。要是有可运行的源码帮助就更大了。

好的,这个机器上没有代码,回去传上来吧。
16 楼 yuyanshan 2010-05-18  
建议楼主附上例子的源代码,看了蛮久是有些困难。要是有可运行的源码帮助就更大了。
15 楼 389yufeng 2010-05-14  
是放困的,看了想睡了.
可能是对哪个什么base库不了解吧
本想加深一点对面向对象的理解,现在看来还是没到哪功底
14 楼 abruzzi 2010-05-13  
JavaScript内核系列 第9章 函数式的Javascript 地址:
http://www.iteye.com/topic/665904
13 楼 abruzzi 2010-05-13  
hyj1254 写道
语法基本上能看懂,设计模式有点晕。
var item = listener.getSense();   

getSense();是哪儿来的啊?
var listeners = {   
    eventType.foo : [   
       {sense : "eventType.foo", handle : function(){doSomething();}}   
       {sense : "eventType.foo", handle : function(){doSomething();}}   
       {sense : "eventType.foo", handle : function(){doSomething();}}   
    ], .....  }

另外和listeners有什么联系?解释一下吧,谢谢。


sense的意思是监听器关注的事件类型,即在新建一个监听器的时候,要指定该监听器想要得到什么类型的通知。每一个listener都有sense属性,getSense是监听器这种对象所具备的。如果listener的sense与事件的type匹配,那么事件分发器在该type的事件发生的时候,会调用监听器的handle方法。这是事件-监听器的基本模式。

比如:
    
    ldmap.addActionListener({  
       sense : uikit.event.EventTypes.EVENT_INDEX_CHANGE,  
       handle : function(event){  
           var idx = event.getObject().index;  
           uikit.component.EventGenerator.raiseEvent({  
              type : uikit.event.EventTypes.EVENT_GRID_DATA_READY,  
              data : {rows : ldmap.get(idx).grid}  
           });  
       }  
    });


传递给ldmap.addActionListener()方法的这个JSON就是一个listener的定义,包括sense和handle两个属性。
12 楼 hyj1254 2010-05-13  
语法基本上能看懂,设计模式有点晕。
var item = listener.getSense();   

getSense();是哪儿来的啊?
var listeners = {   
    eventType.foo : [   
       {sense : "eventType.foo", handle : function(){doSomething();}}   
       {sense : "eventType.foo", handle : function(){doSomething();}}   
       {sense : "eventType.foo", handle : function(){doSomething();}}   
    ], .....  }

另外和listeners有什么联系?解释一下吧,谢谢。
11 楼 meiowei 2010-05-12  
正在看一个javascript的视频,正好结合着LZ的看。thks
10 楼 zhengyutong 2010-05-10  
楼主能否多写一些关于Base的东西?我从来木有用过。
9 楼 steafler 2010-05-10  
学习了,好文章。
8 楼 chenld 2010-05-09  
代码看了,让人有点犯困
7 楼 wusuo513 2010-05-08  
不错的文章,跟踪很久了,望楼主能加快后续章节的发布速度。
6 楼 wwzg99 2010-05-08  
学习了 代码是有点看不懂,
5 楼 abruzzi 2010-05-07  
为何有人投隐藏?隐藏帖是有条件的,我不知道哪儿又触犯这些规则了?
4 楼 abruzzi 2010-05-06  
Rooock 写道
查克拉不够用了..
第一到第七章, 除了正则无视了外, 其他都感觉都能pass.
到了这, 感觉就是犯困, 大段大段的代码.
水平还是木有达到这层次, 勉强不来啊.


呵呵,没那么夸张吧,这些代码不是难,是多!其实我已经裁剪了很多了,贴出来的都是关键代码。你可以参照上篇中关于Base的介绍,从Base的一些简单例子开始看,然后再看下篇中这个比较大的例子,效果应该比较好。
3 楼 Rooock 2010-05-06  
查克拉不够用了..
第一到第七章, 除了正则无视了外, 其他都感觉都能pass.
到了这, 感觉就是犯困, 大段大段的代码.
水平还是木有达到这层次, 勉强不来啊.
2 楼 abruzzi 2010-05-06  
suiye007 写道
我似懂非懂,先收葳一下,回家慢慢的看。。。

最好和上篇http://www.iteye.com/topic/660049结合起来看,这个不全,只是一个示例。

相关推荐

    JavaScript内核系列

    后来,考虑到这个脚本语言的推 广,网景采取了一种宣传策略,将LiveScript更名为JavaScript,目的是为了跟当时非常流行的面向对象语言Java发生暧昧的关系。这种 策略显然颇具成效,以至于到现在很多初学者还会为...

    Delphi 谷歌内核

    Delphi是一种流行的面向对象的编程语言,常常用于创建桌面应用程序。而谷歌内核,即Chromium内核,是Google Chrome浏览器的基础,它提供了一个强大的Web浏览功能的框架。 描述中提到的“直接从官网下载的”,意味着...

    C#MiniBlink内核浏览器_c#miniblink_c#blink_minblink_miniblink浏览器_c#迷你浏

    C#是一种广泛使用的面向对象的编程语言,它具有丰富的库支持和强大的跨平台能力。将MiniBlink与C#结合,可以让我们在.NET环境中构建原生的浏览器应用,享受C#带来的便捷性和高性能。 开发C# MiniBlink内核浏览器的...

    python入门到高级全栈工程师培训 第3期 附课件代码

    第8章 01 上节课复习 02 软件包介绍 03 rpm软件包管理 04 yum软件包管理 05 源码安装python3.5 06 ssh服务 07 apache服务 08 samba服务 第9章 01 Python开发系列课程概要 02 Python作业要求以及博客 03 编程语言...

    基于PHP的拓商企业服务网DT内核php版源码.zip

    2. **数据库模型**:在PHP项目中,数据管理通常是通过ORM(对象关系映射)实现,如Doctrine或Eloquent,这些工具帮助开发者用面向对象的方式来操作数据库。 3. **控制器和视图**:控制器负责处理用户请求,调用业务...

    基于PHP的杰奇2.3内核淡绿唯美小说网站源码 PC+手机版.zip

    开发者可以利用PHP的面向对象编程特性,实现模块化、可维护性强的代码结构。 2. **杰奇内核**:杰奇小说CMS(Content Management System)是一个专门针对小说网站设计的开源系统,它的内核提供了一套完整的后台管理...

    基于PHP的dj舞曲网源码基于dede5.7内核.zip

    PHP支持面向对象编程,拥有丰富的扩展库,能够方便地与各种数据库系统集成,如MySQL,这使得它成为构建动态网站的首选工具。 【压缩包子文件的文件名称列表】中的"132699553284883400"可能代表一个随机生成的文件ID...

    代码之美(中文完整版).pdf

    第8章 图像处理中的即时代码生成 第9章 自顶向下的运算符优先级 9.1. JavaScript 9.2. 符号表 9.3. 语素 9.4. 优先级 9.5. 表达式 9.6. 中置运算符 9.7. 前置操作符 9.8. 赋值运算符 9.9. 常数 9.10. Scope 9.11. ...

    基于PHP的在线网络电视直播内核完整PHP版v3.0源码.zip

    【标签】"php"表明这个项目的核心是PHP编程,开发者可能需要具备PHP基础知识,如语法、面向对象编程、错误处理、函数库调用等。此外,由于涉及到的是网络电视直播,还需要熟悉PHP在处理HTTP请求、响应、会话管理和...

    ActionScript开发技术大全

    第4章ActionScript3.0面向对象编程 74 4.1面向对象编程 74 4.1.1对象的世界 74 4.1.2具体与抽象 75 4.2类 75 4.2.1类的定义 76 4.2.2类的属性 78 4.2.3类的方法 80 4.2.4对象成员与静态成员 83 4.2.5this关键字 84 ...

    用c#写的浏览器源码

    - 类与对象:C#是一种面向对象的语言,浏览器的每个组件,如地址栏、标签页、网页渲染等,都可以通过类来定义。 - 构造函数与析构函数:用于对象的初始化和清理工作。 - 属性和方法:用于定义对象的行为和特性。 ...

    ChromiumFX依赖包

    开发者需要熟悉.NET框架、类库以及面向对象编程的概念,以便有效地使用ChromiumFX。 2. **Chromium Embedded Framework (CEF)**:ChromiumFX是建立在CEF之上的,所以理解CEF的工作原理很重要。CEF允许开发者将...

    Android程序设计基础

    第8章 定位与环境感知 133 8.1 位置,位置,位置 133 8.1.1 我在哪里 135 8.1.2 更新位置 137 8.1.3 模拟说明 138 8.2 充分利用传感器 139 8.2.1 了解传感器 139 8.2.2 解析传感器的读数 140 8.2.3 模拟说明...

    电脑编程有哪几个方向和板块

    - 支持面向对象编程,提高了代码可重用性。 - 类型安全性强,减少了运行时错误。 - 无缝集成.NET框架,提供大量内置功能。 #### 七、PHP - **用途**: - Web开发:动态网站和Web应用。 - 服务器端脚本:处理...

    Linux

    JavaScript语法与C++和Java类似,支持函数式、面向对象和命令式编程范式。它能够实时更新网页内容,实现动态效果,如响应式设计、AJAX(异步JavaScript和XML)请求,以及Websocket等。 在Linux环境中,JavaScript...

    C#web浏览器

    C#是一种由微软公司推出的面向对象的编程语言,它以其强大的功能、高效性和与.NET框架的紧密集成而闻名。C# Web浏览器通常基于.NET Framework或.NET Core构建,利用Windows Presentation Foundation (WPF)或Windows ...

    超级经典程序

    每个经典程序背后都有一系列重要的编程理念、设计原则和技术,例如模块化、面向对象编程、异常处理、并发控制等。通过研究这些程序,我们可以深入理解计算机科学的发展脉络,提高编程技能,并从中获得解决问题的新...

Global site tag (gtag.js) - Google Analytics