轻松掌握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 相关文章推荐
一个多次搜索+多次传值的解决方案
Jan 20 Javascript
<script defer> defer 是什么意思
May 10 Javascript
JQuery中上下文选择器实现方法
May 18 Javascript
JavaScript中Function函数与Object对象的关系
Dec 17 Javascript
深入理解JS中的substr和substring
Apr 26 Javascript
利用select实现年月日三级联动的日期选择效果【推荐】
Dec 13 Javascript
JS中实现函数return多个返回值的实例
Feb 21 Javascript
基于JavaScript实现的顺序查找算法示例
Apr 14 Javascript
JavaScript实现一个空中避难的小游戏
Jun 06 Javascript
使用vuex解决刷新页面state数据消失的问题记录
May 08 Javascript
uniapp 仿微信的右边下拉选择弹出框的实现代码
Jul 12 Javascript
JavaScript实现网页留言板功能
Nov 23 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创建桌面快捷方式实现方法
2015/12/31 PHP
PHP空值检测函数与方法汇总
2017/11/19 PHP
JavaScript入门之基本函数详解
2011/10/21 Javascript
js 关于=+与+=日期函数使用说明(赋值运算符)
2011/11/15 Javascript
js对图片base64编码字符串进行解码并输出图像示例
2014/03/17 Javascript
jQuery实现瀑布流布局
2014/12/12 Javascript
JavaScript汉诺塔问题解决方法
2015/04/21 Javascript
JavaScript实现点击单选按钮改变输入框中文本域内容的方法
2015/08/12 Javascript
jQuery通用的全局遍历方法$.each()用法实例
2016/07/04 Javascript
微信小程序 require机制详解及实例代码
2016/12/14 Javascript
Vue.js展示AJAX数据简单示例讲解
2017/03/29 Javascript
浅析JS中的 map, filter, some, every, forEach, for in, for of 用法总结
2017/03/29 Javascript
Vue 2.0在IE11中打开项目页面空白的问题解决
2017/07/16 Javascript
基于vue-ssr的静态网站生成器VuePress 初体验
2018/04/17 Javascript
vue实现菜单切换功能
2019/05/08 Javascript
Vue 用Vant实现时间选择器的示例代码
2019/10/25 Javascript
用Python制作检测Linux运行信息的工具的教程
2015/04/01 Python
用Python生成器实现微线程编程的教程
2015/04/13 Python
Python中存取文件的4种不同操作
2018/07/02 Python
浅谈python 导入模块和解决文件句柄找不到问题
2018/12/15 Python
在python中使用xlrd获取合并单元格的方法
2018/12/26 Python
Python使用matplotlib绘制三维参数曲线操作示例
2019/09/10 Python
法律工作求职自荐信
2013/10/31 职场文书
质量工程师岗位职责
2013/11/16 职场文书
小学教师师德感言
2014/02/10 职场文书
一帮一活动总结
2014/05/08 职场文书
公司员工离职证明书
2014/10/04 职场文书
教师党员个人自我评价
2015/03/04 职场文书
2015年医生个人工作总结
2015/04/25 职场文书
工作证明格式范文
2015/06/15 职场文书
一个家长教育孩子的心得体会
2016/01/15 职场文书
创业计划书之甜品店
2019/09/18 职场文书
《初涉尘世》读后感3篇
2020/01/10 职场文书
Mysql MVCC机制原理详解
2021/04/20 MySQL
浅谈GO中的Channel以及死锁的造成
2022/03/18 Golang
使用Python开发贪吃蛇游戏 SnakeGame
2022/04/30 Python