轻松掌握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 相关文章推荐
让您的菜单不离网站
Oct 03 Javascript
JQuery 表单中textarea字数限制实现代码
Dec 07 Javascript
AJAX的跨域与JSONP(为文章自动添加短址的功能)
Jan 17 Javascript
你需要知道的10个最佳javascript开发实践小结
Apr 15 Javascript
Atitit.js的键盘按键事件捆绑and事件调度
Apr 01 Javascript
JavaScript验证知识整理
Mar 24 Javascript
微信小程序开发之从相册获取图片 使用相机拍照 本地图片上传
Apr 18 Javascript
详解Vuex中mapState的具体用法
Sep 28 Javascript
AngularJS中控制器函数的定义与使用方法示例
Oct 10 Javascript
解决layui的table插件无法多层级获取json数据的问题
Sep 19 Javascript
浅谈webpack和webpack-cli模块源码分析
Jan 19 Javascript
ant-design-vue 快速避坑指南(推荐)
Jan 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
德生1994机评
2021/03/02 无线电
php按百分比生成缩略图的代码分享
2014/05/10 PHP
php自动更新版权信息显示的方法
2015/06/19 PHP
weiphp微信公众平台授权设置
2016/01/04 PHP
PHP7匿名类用法分析
2016/09/26 PHP
PHP实现多图上传和单图上传功能
2018/05/17 PHP
JS与C#编码解码
2013/12/03 Javascript
一个可以增加和删除行的table并可编辑表格中内容
2014/06/16 Javascript
js判断鼠标左、中、右键哪个被点击的方法
2015/01/27 Javascript
javascript实现校验文件上传控件实例
2015/04/20 Javascript
jquery+ajax+text文本框实现智能提示完整实例
2016/07/09 Javascript
微信小程序之获取当前位置经纬度以及地图显示详解
2017/05/09 Javascript
jQuery+ajax实现修改密码验证功能实例详解
2017/07/06 jQuery
js学习总结之DOM2兼容处理重复问题的解决方法
2017/07/27 Javascript
详解Vue+axios+Node+express实现文件上传(用户头像上传)
2018/08/10 Javascript
JS判断用户用的哪个浏览器实例详解
2018/10/09 Javascript
jQuery位置选择器用法实例分析
2019/06/28 jQuery
vue + typescript + video.js实现 流媒体播放 视频监控功能
2019/07/07 Javascript
node.js处理前端提交的GET请求
2019/08/30 Javascript
JavaScript中reduce()的5个基本用法示例
2020/07/19 Javascript
[05:13]TI4 中国战队 机场出征!!
2014/07/07 DOTA
老生常谈python之鸭子类和多态
2017/06/13 Python
pandas 对series和dataframe进行排序的实例
2018/06/09 Python
python实现淘宝购物系统
2019/10/25 Python
遮罩层 + Iframe实现界面自动显示的示例代码
2020/04/26 HTML / CSS
日本亚马逊官方网站:Amazon.co.jp
2020/04/14 全球购物
C#的几个面试问题
2016/05/22 面试题
《记承天寺夜游》教学反思
2014/02/16 职场文书
大学生就业求职信
2014/06/12 职场文书
单位消防安全责任书
2014/07/23 职场文书
出纳试用期自我鉴定范文
2014/09/16 职场文书
2016年寒假社会实践活动总结
2015/03/27 职场文书
2015年组织部工作总结
2015/04/03 职场文书
电子表的操作介绍说明书
2019/10/28 职场文书
python 如何在 Matplotlib 中绘制垂直线
2021/04/02 Python
Python中使用subprocess库创建附加进程
2021/05/11 Python