深入理解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 相关文章推荐
JQuery EasyUI 对话框的使用方法
Oct 24 Javascript
JQUERY实现左侧TIPS滑进滑出效果示例
Jun 27 Javascript
jquery实现select选中行、列合计示例
Apr 25 Javascript
基于Jquery+Ajax+Json实现分页显示附效果图
Jul 30 Javascript
JS+DIV+CSS实现仿表单下拉列表效果
Aug 18 Javascript
javascript每日必学之条件分支
Feb 17 Javascript
jquery按回车键实现表单提交的简单实例
May 25 Javascript
详解JavaScript权威指南之对象
Sep 27 Javascript
解析预加载显示图片艺术
Dec 05 Javascript
详解Angular 4.x 动态创建组件
Apr 25 Javascript
引入JavaScript时alert弹出框显示中文乱码问题
Sep 16 Javascript
Vuex中mutations与actions的区别详解
Mar 01 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获取当前文件所在目录 getcwd()函数
2009/05/13 PHP
php 获取远程网页内容的函数
2009/09/08 PHP
php生成随机字符串可指定纯数字、纯字母或者混合的
2014/04/18 PHP
php实现压缩合并js的方法【附demo源码下载】
2016/09/22 PHP
PHP截取发动短信内容的方法
2017/07/04 PHP
yii gridview实现时间段筛选功能
2017/08/15 PHP
用javascript实现页面打印的三种方法
2007/03/05 Javascript
对YUI扩展的Gird组件 Part-1
2007/03/10 Javascript
实例分析javascript中的call()和apply()方法
2014/11/28 Javascript
使用jquery实现仿百度自动补全特效
2015/07/23 Javascript
JavaScript中几种排序算法的简单实现
2015/07/29 Javascript
论JavaScript模块化编程
2016/03/07 Javascript
javascript 常用验证函数总结
2016/06/28 Javascript
利用Node.js对文件进行重命名
2017/03/12 Javascript
JavaScript实现获取远程的html到当前页面中
2017/03/26 Javascript
AngularJS 实现点击按钮获取验证码功能实例代码
2017/07/13 Javascript
vue项目中导入swiper插件的方法
2018/01/30 Javascript
node下使用UglifyJS压缩合并JS文件的方法
2018/03/07 Javascript
浅谈node.js 命令行工具(cli)
2018/05/10 Javascript
vue的全局变量和全局拦截请求器的示例代码
2018/09/13 Javascript
Python编写百度贴吧的简单爬虫
2015/04/02 Python
Python 中Django安装和使用教程详解
2019/07/03 Python
ipad上运行python的方法步骤
2019/10/12 Python
python基于K-means聚类算法的图像分割
2019/10/30 Python
jupyter notebook 重装教程
2020/04/16 Python
PyTorch中torch.tensor与torch.Tensor的区别详解
2020/05/18 Python
如何在python中判断变量的类型
2020/07/29 Python
H5新属性audio音频和video视频的控制详解(推荐)
2016/12/09 HTML / CSS
香港演唱会订票网站:StubHub香港
2019/10/10 全球购物
五年级音乐教学反思
2014/02/06 职场文书
安全责任协议书
2014/04/21 职场文书
国庆65周年演讲稿:回首往昔,展望未来
2014/09/21 职场文书
2014年学生党支部工作总结
2014/12/20 职场文书
教师学期述职自我鉴定
2019/08/16 职场文书
vue项目多环境配置(.env)的实现
2021/07/21 Vue.js
基于Python和openCV实现图像的全景拼接详细步骤
2021/10/05 Python