轻松掌握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 相关文章推荐
通过隐藏option实现select的联动效果
Nov 10 Javascript
Javascript WebSocket使用实例介绍(简明入门教程)
Apr 16 Javascript
js 左右悬浮对联广告特效代码
Dec 12 Javascript
jquery读取xml文件实现省市县三级联动的方法
May 29 Javascript
jQuery预加载图片常用方法
Jun 15 Javascript
jquery分割字符串的方法
Jun 24 Javascript
ReactJs设置css样式的方法
Jun 08 Javascript
关于vue的语法规则检测报错问题的解决
May 21 Javascript
深入理解react 组件类型及使用场景
Mar 07 Javascript
在Chrome DevTools中调试JavaScript的实现
Apr 07 Javascript
基于ajax及jQuery实现局部刷新过程解析
Sep 12 jQuery
js实现mp3录音通过websocket实时传送+简易波形图效果
Jun 12 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 错误之引号中使用变量
2009/05/04 PHP
shopex中集成的站长统计功能的代码简单分析
2011/08/11 PHP
优化PHP程序的方法小结
2012/02/23 PHP
php 判断是否是中文/英文/数字示例代码
2013/09/30 PHP
php简单备份与还原MySql的方法
2016/05/09 PHP
php获取当前月与上个月月初及月末时间戳的方法
2016/12/05 PHP
PHP实现微信JS-SDK接口选择相册及拍照并上传的方法
2016/12/05 PHP
IE6中链接A的href为javascript协议时不在当前页面跳转
2014/06/05 Javascript
JQEasy-ui在IE9以下版本中二次加载的问题分析及处理方法
2014/06/23 Javascript
js中实现多态采用和继承类似的方法
2014/08/22 Javascript
jQuery实现炫酷的鼠标轨迹特效
2015/02/01 Javascript
JavaScript、tab切换完整版(自动切换、鼠标移入停止、移开运行)
2016/01/05 Javascript
仿Angular Bootstrap TimePicker创建分钟数-秒数的输入控件
2016/07/01 Javascript
javascript弹出带文字信息的提示框效果
2016/07/19 Javascript
js根据json数据中的某一个属性来给数据分组的方法
2018/10/08 Javascript
Vue-cli3.x + axios 跨域方案踩坑指北
2019/07/04 Javascript
layui 实现二级弹窗弹出之后 关闭一级弹窗的方法
2019/09/18 Javascript
vue实现折线图 可按时间查询
2020/08/21 Javascript
在Linux下使用Python的matplotlib绘制数据图的教程
2015/06/11 Python
Python数据类型学习笔记
2016/01/13 Python
Python实现PS滤镜的万花筒效果示例
2018/01/23 Python
Django RBAC权限管理设计过程详解
2019/08/06 Python
Python全栈之列表数据类型详解
2019/10/01 Python
Python闭包与装饰器原理及实例解析
2020/04/30 Python
Python调用REST API接口的几种方式汇总
2020/10/19 Python
马来西亚在线时尚女装商店:KEI MAG
2017/09/28 全球购物
简历中求职的个人自我评价
2013/12/03 职场文书
入学申请自荐信范文
2014/02/26 职场文书
经济信息系毕业生自荐信
2014/06/02 职场文书
物业管理专业自荐信
2014/07/01 职场文书
大学生学期个人总结
2015/02/12 职场文书
国家助学贷款承诺书
2015/04/30 职场文书
党支部审查意见
2015/06/02 职场文书
我的中国梦心得体会范文
2016/01/05 职场文书
信息技术远程培训心得体会
2016/01/09 职场文书
技术转让协议书
2016/03/19 职场文书