深入理解JavaScript系列(37):设计模式之享元模式详解


Posted in Javascript onMarch 04, 2015

介绍

享元模式(Flyweight),运行共享技术有效地支持大量细粒度的对象,避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类)。

享元模式可以避免大量非常相似类的开销,在程序设计中,有时需要生产大量细粒度的类实例来表示数据,如果能发现这些实例除了几个参数以外,开销基本相同的 话,就可以大幅度较少需要实例化的类的数量。如果能把那些参数移动到类实例的外面,在方法调用的时候将他们传递进来,就可以通过共享大幅度第减少单个实例 的数目。

那么如果在JavaScript中应用享元模式呢?有两种方式,第一种是应用在数据层上,主要是应用在内存里大量相似的对象上;第二种是应用在DOM层上,享元可以用在中央事件管理器上用来避免给父容器里的每个子元素都附加事件句柄。

享元与数据层

Flyweight中有两个重要概念--内部状态intrinsic和外部状态extrinsic之分,内部状态就是在对象里通过内部方法管理,而外部信息可以在通过外部删除或者保存。

说白点,就是先捏一个的原始模型,然后随着不同场合和环境,再产生各具特征的具体模型,很显然,在这里需要产生不同的新对象,所以Flyweight模式中常出现Factory模式,Flyweight的内部状态是用来共享的,Flyweight factory负责维护一个Flyweight pool(模式池)来存放内部状态的对象。

使用享元模式

让我们来演示一下如果通过一个类库让系统来管理所有的书籍,每个书籍的元数据暂定为如下内容:

ID

Title

Author

Genre

Page count

Publisher ID

ISBN

我们还需要定义每本书被借出去的时间和借书人,以及退书日期和是否可用状态:
checkoutDate

checkoutMember

dueReturnDate

availability

因为book对象设置成如下代码,注意该代码还未被优化:
var Book = function( id, title, author, genre, pageCount,publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate,availability ){

   this.id = id;

   this.title = title;

   this.author = author;

   this.genre = genre;

   this.pageCount = pageCount;

   this.publisherID = publisherID;

   this.ISBN = ISBN;

   this.checkoutDate = checkoutDate;

   this.checkoutMember = checkoutMember;

   this.dueReturnDate = dueReturnDate;

   this.availability = availability;

};

Book.prototype = {

   getTitle:function(){

       return this.title;

   },

   getAuthor: function(){

       return this.author;

   },

   getISBN: function(){

       return this.ISBN;

   },

/*其它get方法在这里就不显示了*/
// 更新借出状态

updateCheckoutStatus: function(bookID, newStatus, checkoutDate,checkoutMember, newReturnDate){

   this.id  = bookID;

   this.availability = newStatus;

   this.checkoutDate = checkoutDate;

   this.checkoutMember = checkoutMember;

   this.dueReturnDate = newReturnDate;

},

//续借

extendCheckoutPeriod: function(bookID, newReturnDate){

    this.id =  bookID;

    this.dueReturnDate = newReturnDate;

},

//是否到期

isPastDue: function(bookID){

   var currentDate = new Date();

   return currentDate.getTime() > Date.parse(this.dueReturnDate);

 }

};

程序刚开始可能没问题,但是随着时间的增加,图书可能大批量增加,并且每种图书都有不同的版本和数量,你将会发现系统变得越来越慢。几千个book对象在内存里可想而知,我们需要用享元模式来优化。

我们可以将数据分成内部和外部两种数据,和book对象相关的数据(title, author 等)可以归结为内部属性,而(checkoutMember, dueReturnDate等)可以归结为外部属性。这样,如下代码就可以在同一本书里共享同一个对象了,因为不管谁借的书,只要书是同一本书,基本信息是一样的:

/*享元模式优化代码*/

var Book = function(title, author, genre, pageCount, publisherID, ISBN){

   this.title = title;

   this.author = author;

   this.genre = genre;

   this.pageCount = pageCount;

   this.publisherID = publisherID;

   this.ISBN = ISBN;

};

定义基本工厂

让我们来定义一个基本工厂,用来检查之前是否创建该book的对象,如果有就返回,没有就重新创建并存储以便后面可以继续访问,这确保我们为每一种书只创建一个对象:

/* Book工厂 单例 */

var BookFactory = (function(){

   var existingBooks = {};

   return{

       createBook: function(title, author, genre,pageCount,publisherID,ISBN){

       /*查找之前是否创建*/

           var existingBook = existingBooks[ISBN];

           if(existingBook){

                   return existingBook;

               }else{

               /* 如果没有,就创建一个,然后保存*/

               var book = new Book(title, author, genre,pageCount,publisherID,ISBN);

               existingBooks[ISBN] =  book;

               return book;

           }

       }

   }

});

管理外部状态
外部状态,相对就简单了,除了我们封装好的book,其它都需要在这里管理:
/*BookRecordManager 借书管理类 单例*/

var BookRecordManager = (function(){

   var bookRecordDatabase = {};

   return{

       /*添加借书记录*/

       addBookRecord: function(id, title, author, genre,pageCount,publisherID,ISBN, checkoutDate, checkoutMember, dueReturnDate, availability){

           var book = bookFactory.createBook(title, author, genre,pageCount,publisherID,ISBN);

            bookRecordDatabase[id] ={

               checkoutMember: checkoutMember,

               checkoutDate: checkoutDate,

               dueReturnDate: dueReturnDate,

               availability: availability,

               book: book;
           };

       },

    updateCheckoutStatus: function(bookID, newStatus, checkoutDate, checkoutMember,     newReturnDate){

        var record = bookRecordDatabase[bookID];

        record.availability = newStatus;

        record.checkoutDate = checkoutDate;

        record.checkoutMember = checkoutMember;

        record.dueReturnDate = newReturnDate;

   },

   extendCheckoutPeriod: function(bookID, newReturnDate){

       bookRecordDatabase[bookID].dueReturnDate = newReturnDate;

   },

   isPastDue: function(bookID){

       var currentDate = new Date();

       return currentDate.getTime() > Date.parse(bookRecordDatabase[bookID].dueReturnDate);

   }

 };

});

通过这种方式,我们做到了将同一种图书的相同信息保存在一个bookmanager对象里,而且只保存一份;相比之前的代码,就可以发现节约了很多内存。

享元模式与DOM

关于DOM的事件冒泡,在这里就不多说了,相信大家都已经知道了,我们举两个例子。

例1:事件集中管理

举例来说,如果我们又很多相似类型的元素或者结构(比如菜单,或者ul里的多个li)都需要监控他的click事件的话,那就需要多每个元素进行事件绑定,如果元素有非常非常多,那性能就可想而知了,而结合冒泡的知识,任何一个子元素有事件触发的话,那触发以后事件将冒泡到上一级元素,所以利用这个特性,我们可以使用享元模式,我们可以对这些相似元素的父级元素进行事件监控,然后再判断里面哪个子元素有事件触发了,再进行进一步的操作。

在这里我们结合一下jQuery的bind/unbind方法来举例。

HTML:

<div id="container">

   <div class="toggle" href="#">更多信息 (地址)

       <span class="info">

          这里是更多信息

       </span></div>

   <div class="toggle" href="#">更多信息 (地图)

       <span class="info">

          <iframe src="http://www.map-generator.net/extmap.php?name=London&address=london%2C%20england&width=500...gt;"</iframe>

       </span>

   </div>

</div>

JavaScript:
stateManager = {

   fly: function(){

       var self =  this;

       $('#container').unbind().bind("click", function(e){

           var target = $(e.originalTarget || e.srcElement);

           // 判断是哪一个子元素

           if(target.is("div.toggle")){

               self.handleClick(target);

           }

       });

   },
   handleClick: function(elem){

       elem.find('span').toggle('slow');

   }

});

例2:应用享元模式提升性能

另外一个例子,依然和jQuery有关,一般我们在事件的回调函数里使用元素对象是会后,经常会用到$(this)这种形式,其实它重复创建了新对象,因为本身回调函数里的this已经是DOM元素自身了,我们必要必要使用如下这样的代码:

$('div').bind('click', function(){

 console.log('You clicked: ' + $(this).attr('id'));

});

// 上面的代码,要避免使用,避免再次对DOM元素进行生成jQuery对象,因为这里可以直接使用DOM元素自身了。

$('div').bind('click', function(){

 console.log('You clicked: ' + this.id);

});

其实,如果非要用$(this)这样的形式,我们也可以实现自己版本的单实例模式,比如我们来实现一个jQuery.signle(this)这样的函数以便返回DOM元素自身:
jQuery.single = (function(o){
   var collection = jQuery([1]);

   return function(element) {
       // 将元素放到集合里

       collection[0] = element;
        // 返回集合

       return collection;
   };

 });

 

使用方法:
$('div').bind('click', function(){

   var html = jQuery.single(this).next().html();

   console.log(html);

 });

这样,就是原样返回DOM元素自身了,而且不进行jQuery对象的创建。

总结

Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度.应用场合很多:比如你要从一个数据库中读取一系列字符串,这些字符串中有许多是重复的,那么我们可以将这些字符串储存在Flyweight池(pool)中。

如果一个应用程序使用了大量的对象,而这些大量的对象造成了很大的存储开心时就应该考虑使用享元模式;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么就可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。

Javascript 相关文章推荐
再次更新!MSClass (Class Of Marquee Scroll通用不间断滚动JS封装类 Ver 1.6)
Feb 05 Javascript
jQuery + Flex 通过拖拽方式动态改变图片的代码
Aug 03 Javascript
js 判断文件类型并控制表单提交示例代码
Nov 14 Javascript
js判断字符长度及中英文数字等
Mar 19 Javascript
js实现的早期滑动门菜单效果代码
Aug 27 Javascript
纯javascript实现自动发送邮件
Oct 21 Javascript
JavaScript实现经典排序算法之选择排序
Dec 28 Javascript
详解Vue-基本标签和自定义控件
Mar 24 Javascript
详解tween.js的使用教程
Sep 14 Javascript
vue webpack开发访问后台接口全局配置的方法
Sep 18 Javascript
微信小程序时间标签和时间范围的联动效果
Feb 15 Javascript
详解vue-flickity的fullScreen功能实现
Apr 07 Javascript
jQuery插件开发的五种形态小结
Mar 04 #Javascript
深入理解JavaScript系列(36):设计模式之中介者模式详解
Mar 04 #Javascript
百度UEditor编辑器如何关闭抓取远程图片功能
Mar 03 #Javascript
jQuery实现复选框成对选择及对应取消的方法
Mar 03 #Javascript
js实现文本框中输入文字页面中div层同步获取文本框内容的方法
Mar 03 #Javascript
JS实现文字放大效果的方法
Mar 03 #Javascript
jQuery实现的感应鼠标悬停图片色彩渐显效果
Mar 03 #Javascript
You might like
用PHP实现图象锐化代码
2007/06/14 PHP
ThinkPHP使用smarty模板引擎的方法
2014/07/01 PHP
PHP中使用file_get_contents post数据代码例子
2015/02/13 PHP
Laravel 5 框架入门(三)
2015/04/09 PHP
PDO::errorCode讲解
2019/01/28 PHP
Jquery Ajax 学习实例2 向页面发出请求 返回JSon格式数据
2010/03/15 Javascript
js实现翻页后保持checkbox选中状态的实现方法
2012/11/03 Javascript
js对列表中第一个值处理与jsp页面对列表中第一个值处理的区别详解
2013/11/05 Javascript
js如何实现点击标签文字,文字在文本框出现
2015/08/05 Javascript
JavaScript如何动态创建table表格
2020/08/02 Javascript
javascript实现二级级联菜单的简单制作
2015/11/19 Javascript
NodeJS中的MongoDB快速入门详细教程
2016/11/11 NodeJs
详解vue与后端数据交互(ajax):vue-resource
2017/03/16 Javascript
Vue Promise的axios请求封装详解
2018/08/13 Javascript
基于vue+uniapp直播项目实现uni-app仿抖音/陌陌直播室功能
2019/11/12 Javascript
JavaScript 实现HTML DOM增删改查操作的常见方法详解
2020/01/04 Javascript
[02:00]DOTA2英雄COSPLAY闹市街头巡游助威2015国际邀请赛
2015/08/02 DOTA
scrapy自定义pipeline类实现将采集数据保存到mongodb的方法
2015/04/16 Python
Python3中的2to3转换工具使用示例
2015/06/12 Python
总结网络IO模型与select模型的Python实例讲解
2016/06/27 Python
Python对文件和目录进行操作的方法(file对象/os/os.path/shutil 模块)
2017/05/08 Python
利用Opencv中Houghline方法实现直线检测
2018/02/11 Python
python spyder中读取txt为图片的方法
2018/04/27 Python
Python子类继承父类构造函数详解
2019/02/19 Python
pyqt5 实现工具栏文字图片同时显示
2019/06/13 Python
对django的User模型和四种扩展/重写方法小结
2019/08/17 Python
python 并发编程 多路复用IO模型详解
2019/08/20 Python
基于 Python 实践感知器分类算法
2021/01/07 Python
基础的CSS3弹性盒Flexbox布局使用实例
2016/04/08 HTML / CSS
HomeAway英国:全球领先的度假租赁在线市场
2020/02/03 全球购物
外贸主管求职简历的自我评价
2013/10/23 职场文书
坚守艰苦奋斗精神坚决反对享乐主义整改措施
2014/09/17 职场文书
老人院义工活动感想
2015/08/07 职场文书
2016年党员岗位承诺书
2016/03/24 职场文书
浅谈Redis的几个过期策略
2021/05/27 Redis
Golang 切片(Slice)实现增删改查
2022/04/22 Golang