轻松掌握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 相关文章推荐
使用js检测浏览器的实现代码
May 14 Javascript
javascript封装的sqlite操作类实例
Jul 17 Javascript
js实现TAB切换对应不同颜色的代码
Aug 31 Javascript
javascript实现日期三级联动下拉框选择菜单
Dec 03 Javascript
bootstrap daterangepicker汉化以及扩展功能
Jun 15 Javascript
vue中post请求以a=a&b=b 的格式写遇到的问题
Apr 27 Javascript
微信小程序实现红包功能(后端PHP实现逻辑)
Jul 11 Javascript
利用JS动态生成隔行换色HTML表格的两种方法
Oct 09 Javascript
JavaScript之解构赋值的理解
Jan 30 Javascript
微信小程序利用for循环解决内容变更问题
Mar 05 Javascript
vue+canvas实现拼图小游戏
Sep 18 Javascript
JS实现简单九宫格抽奖
Jun 28 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与php MySQL 之间的关系
2009/07/17 PHP
php根据操作系统转换文件名大小写的方法
2014/02/24 PHP
WAMP环境中扩展oracle函数库(oci)
2015/06/26 PHP
完美解决在ThinkPHP控制器中命名空间的问题
2017/05/05 PHP
PHP类的自动加载机制实现方法分析
2019/01/10 PHP
JavaScript.The.Good.Parts阅读笔记(二)作用域&闭包&减缓全局空间污染
2010/11/16 Javascript
如何使用jQuery Draggable和Droppable实现拖拽功能
2013/07/05 Javascript
用jQuery与JSONP轻松解决跨域访问的问题
2014/02/04 Javascript
js文件包含的几种方式介绍
2014/09/28 Javascript
原生JS实现响应式瀑布流布局
2015/04/02 Javascript
浅谈jQuery效果函数
2016/09/16 Javascript
jquery.validate[.unobtrusive]和Bootstrap实现tooltip错误提示问题分析
2016/10/30 Javascript
jQuery实现倒计时(倒计时年月日可自己输入)
2016/12/02 Javascript
nodejs实现邮件发送服务实例分享
2017/03/29 NodeJs
angularJS1 url中携带参数的获取方法
2018/10/09 Javascript
Layui实现主窗口和Iframe层参数传递
2019/11/14 Javascript
Python3实现Web网页图片下载
2016/01/28 Python
Python创建普通菜单示例【基于win32ui模块】
2018/05/09 Python
TensorFlow的权值更新方法
2018/06/14 Python
基于树莓派的语音对话机器人
2019/06/17 Python
基于Python实现签到脚本过程解析
2019/10/25 Python
三个python爬虫项目实例代码
2019/12/28 Python
如何使用python实现模拟鼠标点击
2020/01/06 Python
PyTorch 随机数生成占用 CPU 过高的解决方法
2020/01/13 Python
Python要如何实现列表排序的几种方法
2020/02/21 Python
使用python实现多维数据降维操作
2020/02/24 Python
pyinstaller将含有多个py文件的python程序做成exe
2020/04/29 Python
如何把外网python虚拟环境迁移到内网
2020/05/18 Python
python用Tkinter做自己的中文代码编辑器
2020/09/07 Python
Python hashlib模块的使用示例
2020/10/09 Python
Python try except else使用详解
2021/01/12 Python
亚洲颇具影响力的男性在线购物零售商:His
2019/11/24 全球购物
会计电算化学生个人的自我评价
2014/02/08 职场文书
新学期教师寄语
2014/04/02 职场文书
2014年管理人员工作总结
2014/12/01 职场文书
2015年幼儿教育工作总结
2015/07/24 职场文书