轻松掌握JavaScript享元模式


Posted in Javascript onAugust 27, 2016

在JavaScript中,浏览器特别是移动端的浏览器分配的内存很有限,如何节省内存就成了一件非常有意义的事情。节省内存的一个有效方法是减少对象的数量。 

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

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

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

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

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

我们可以将内部状态相同的所有对象替换为同一个共享对象,而要创建这样一个共享对象就需要用到单例工厂方法,而不是普通的构造函数,这样做可以跟踪到已经实例化的各个对象,从而仅当所需对象的内部状态不同于已有对象时才创建一个新对象。对象的外在状态被保存在一个管理器对象中。在调用对象的方法时,管理器会把这些外在状态作为参数传入。 

把一个对象的数据保存在两个不同的对象中(共享对象、管理器对象)
 1.共享对象(享元对象)
 2.单例工厂方法(创建共享对象)
 3.管理器对象(管理外部状态) 

比如图书馆中的一本书可以用一个对象来表示,他有很多属性

var Book = function( id, title, author, genre, pageCount,publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate,availability ){
  ...//初始化代码
}
Book.prototype = {
  getTitle:function(){
    return this.title;
  },
  ...
  // 更新借出状态方法
  updateCheckoutStatus:function(bookID, newStatus, checkoutDate,checkoutMember, newReturnDate){...},
  //续借
  extendCheckoutPeriod: function(bookID, newReturnDate){...},
  //是否到期
  isPastDue: function(bookID){...}
}

程序刚开始可能没问题,但是随着时间的增加,图书可能大批量增加,并且每种图书都有不同的版本和数量,你将会发现系统变得越来越慢。几千个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对象里,而且只保存一份;相比之前的代码,就可以发现节约了很多内存。 

对象池 
对象池是另外一种性能优化方案,和享元模式有一些相似之处,但没有分离内部状态和外部状态这个过程。 
通用对象池实现:

var objectPoolFactory = function (createObjFn) {
  var objectPool = []; //对象池
  return {
    create: function () { //取出
      var obj = objectPool.length === 0 ? createObjFn.apply(this,arguments) : objectPool.shift();
      return obj;
    },
    recover: function (obj) { //收回
      objectPool.push(obj);
    }
  }
};

现在利用objectPoolFactory来创建一个装载一些iframe的对象池:

var iframeFactory = objectPoolFactory(function () {
  var iframe = document.createElement('iframe');
  document.body.appendChild(iframe);
  iframe.onload = function () {
    iframe.onload = null; //防止iframe重复加载的bug
    iframeFactory.recover(iframe); //iframe加载完成后往对象池填回节点(收回)
  };
  return iframe;
});
//调用
var iframe1 = iframeFactory.create();
iframe1.src = 'http://www.qq.com';

参考文献: 《JavaScript模式》 《JavaScript设计模式与开发实践》

 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Javascript中的getUTCDay()方法使用详解
Jun 10 Javascript
AngularJS实现数据列表的增加、删除和上移下移等功能实例
Sep 05 Javascript
使用use注册Vue全局组件和全局指令的方法
Mar 08 Javascript
使用 vue-i18n 切换中英文效果
May 23 Javascript
vue之将echart封装为组件
Jun 02 Javascript
Node.js中的不安全跳转如何防御详解
Oct 21 Javascript
Jquery遍历筛选数组的几种方法和遍历解析json对象,Map()方法详解以及数组中查询某值是否存在
Jan 18 jQuery
async/await优雅的错误处理方法总结
Jan 30 Javascript
vue.js实现只能输入数字的输入框
Oct 19 Javascript
js+audio实现音乐播放器
Sep 13 Javascript
微信小程序实现日历签到
Sep 21 Javascript
js前端图片加载异常兜底方案
Jun 21 Javascript
JavaScript编码风格指南(中文版)
Aug 26 #Javascript
JavaScript使用forEach()与jQuery使用each遍历数组时return false 的区别
Aug 26 #Javascript
ES6中的数组扩展方法
Aug 26 #Javascript
jQuery实现微信长按识别二维码功能
Aug 26 #Javascript
XMLHttpRequest Level 2 使用指南
Aug 26 #Javascript
js HTML5多图片上传及预览实例解析(不含前端的文件分割)
Aug 26 #Javascript
JavaScript组合模式学习要点
Aug 26 #Javascript
You might like
PHP邮件专题
2006/10/09 PHP
PHP+memcache实现消息队列案例分享
2014/05/21 PHP
php判断一个数组是否为有序的方法
2015/03/27 PHP
PHP输出缓冲控制Output Control系列函数详解
2015/07/02 PHP
JQuery Tips(2) 关于$()包装集你不知道的
2009/12/14 Javascript
js 数组克隆方法 小结
2010/03/20 Javascript
js获取location.href的参数实例代码
2013/08/02 Javascript
从jquery的过滤器.filter()方法想到的
2013/09/29 Javascript
javascript数组操作总结和属性、方法介绍
2014/04/05 Javascript
js创建一个input数组并绑定click事件的方法
2014/06/12 Javascript
浅析Node.js查找字符串功能
2014/09/03 Javascript
JS JQUERY实现滚动条自动滚到底的方法
2015/01/09 Javascript
Linux下为Node.js程序配置MySQL或Oracle数据库的方法
2016/03/19 Javascript
如何在Linux上安装Node.js
2016/04/01 Javascript
animate 实现滑动切换效果【实例代码】
2016/05/05 Javascript
Angular中$broadcast和$emit的使用方法详解
2017/05/22 Javascript
全选复选框JavaScript编写小结(附代码)
2017/08/16 Javascript
使用nodeJs来安装less及编译less文件为css文件的方法
2017/11/20 NodeJs
jQuery实现弹窗下底部页面禁止滑动效果
2017/12/19 jQuery
微信小程序数字滚动插件使用详解
2018/02/02 Javascript
vue父组件向子组件传递多个数据的实例
2018/03/01 Javascript
vue组件中iview的modal组件爬坑问题之modal的显示与否应该是使用v-show
2019/04/12 Javascript
浅谈JS的原型和继承
2019/05/08 Javascript
浅析vue-cli3配置webpack-bundle-analyzer插件【推荐】
2019/10/23 Javascript
React中获取数据的3种方法及优缺点
2020/02/18 Javascript
[02:56]DOTA2上海特锦赛小组赛解说FreeAgain采访花絮
2016/02/27 DOTA
[06:43]2018DOTA2国际邀请赛寻真——VGJ.Thunder
2018/08/11 DOTA
matplotlib绘制动画代码示例
2018/01/02 Python
python如何提取英语pdf内容并翻译
2020/03/03 Python
Python根据指定文件生成XML的方法
2020/06/29 Python
完美解决IE8下不兼容rgba()的问题
2017/03/31 HTML / CSS
求职简历中自我评价
2014/01/28 职场文书
助理政工师申报材料
2014/06/03 职场文书
安全责任书模板
2014/07/22 职场文书
绿里奇迹观后感
2015/06/15 职场文书
Python 全局空间和局部空间
2022/04/06 Python