轻松掌握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 相关文章推荐
最新的10款jQuery内容滑块插件分享
Sep 18 Javascript
JS中的form.submit()不能提交表单的错误原因
Oct 08 Javascript
javascript下拉框选项单击事件的例子分享
Mar 04 Javascript
整理AngularJS框架使用过程当中的一些性能优化要点
Mar 05 Javascript
BootStrap3学习笔记(一)之网格系统
May 20 Javascript
D3.js封装文本实现自动换行和旋转平移等功能
Oct 14 Javascript
解析javascript图片懒加载与预加载的分析总结
Oct 27 Javascript
webpack4 css打包压缩问题的解决
May 18 Javascript
详解js访问对象的属性和方法
Oct 25 Javascript
laydate只显示时分 不显示秒的功能实现方法
Sep 28 Javascript
element-ui封装一个Table模板组件的示例
Jan 04 Javascript
vue 中this.$set 动态绑定数据的案例讲解
Jan 29 Vue.js
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
【COS正片】蕾姆睡衣cos,纯洁可爱被治愈了 cn名濑弥七
2020/03/02 日漫
使用PHP数组实现无限分类,不使用数据库,不使用递归.
2006/12/09 PHP
PHP 字符串 小常识
2009/06/05 PHP
php去除重复字的实现代码
2011/09/16 PHP
PHP+JQuery+Ajax实现分页方法详解
2016/08/06 PHP
php微信开发之关注事件
2018/06/14 PHP
PHP中如何使用Redis接管文件存储Session详解
2018/11/28 PHP
使用Entrust扩展包在laravel 中实现RBAC的功能
2020/03/16 PHP
jquery不会自动回收xmlHttpRequest对象 导致了内存溢出
2012/06/18 Javascript
无缝滚动改进版支持上下左右滚动(封装成函数)
2012/12/04 Javascript
JQueryEasyUI Layout布局框架的使用
2013/04/08 Javascript
jquery+css+ul模拟列表菜单具体实现思路
2013/04/15 Javascript
jquery实现文字由下到上循环滚动的实例代码
2013/08/09 Javascript
单击和双击事件的冲突处理示例代码
2014/04/03 Javascript
JS使用ajax从xml文件动态获取数据显示的方法
2015/03/24 Javascript
简介JavaScript中Boolean.toSource()方法的使用
2015/06/05 Javascript
jQuery EasyUI 页面加载等待及页面等待层
2017/02/06 Javascript
vue给input file绑定函数获取当前上传的对象完美实现方法
2017/12/15 Javascript
vue定义全局变量和全局方法的方法示例
2018/08/01 Javascript
layDate日期控件使用方法详解
2018/11/15 Javascript
详细讲解如何创建, 发布自己的 Vue UI 组件库
2019/05/29 Javascript
编写Python脚本批量下载DesktopNexus壁纸的教程
2015/05/06 Python
Python socket模块实现的udp通信功能示例
2019/04/10 Python
python用WxPython库实现无边框窗体和透明窗体实现方法详解
2020/02/21 Python
Pytorch之扩充tensor的操作
2021/03/04 Python
CSS3制作彩色进度条样式的代码示例分享
2016/06/23 HTML / CSS
Carter’s OshKosh加拿大:购买婴幼儿服装和童装
2018/11/27 全球购物
匡威英国官网:Converse英国
2018/12/02 全球购物
Lentiamo丹麦:购买便宜的隐形眼镜
2021/01/13 全球购物
澳洲的UGG雪地靴超级市场:Uggs.com.au
2020/04/06 全球购物
命名空间(namespace)和程序集(Assembly)有什么区别
2015/09/25 面试题
竞选劳动委员演讲稿
2014/04/28 职场文书
春节联欢会策划方案
2014/05/16 职场文书
协议书格式模板
2016/03/24 职场文书
uniapp开发小程序的经验总结
2021/04/08 Javascript
如何使用Python提取Chrome浏览器保存的密码
2021/06/09 Python