总结JavaScript设计模式编程中的享元模式使用


Posted in Javascript onMay 21, 2016

享元模式不同于一般的设计模式,它主要用来优化程序的性能,它最适合解决大量类似的对象而产生的性能问题。享元模式通过分析应用程序的对象,将其解析为内在数据和外在数据,减少对象的数量,从而提高应用程序的性能。

基本知识

享元模式通过共享大量的细粒度的对象,减少对象的数量,从而减少对象的内存,提高应用程序的性能。其基本思想就是分解现有类似对象的组成,将其展开为可以共享的内在数据和不可共享的外在数据,我们称内在数据的对象为享元对象。通常还需要一个工厂类来维护内在数据。
在JS中,享元模式主要有下面几个角色组成:
(1)客户端:用来调用享元工厂来获取内在数据的类,通常是应用程序所需的对象,
(2)享元工厂:用来维护享元数据的类
(3)享元类:保持内在数据的类

享元模式的实现和应用

一般实现

我们举个例子进行说明:苹果公司批量生产iphone,iphone的大部分数据比如型号,屏幕都是一样,少数部分数据比如内存有分16G,32G等。未使用享元模式前,我们写代码如下:

function Iphone(model, screen, memory, SN) {
  this. model = model;
  this.screen = screen;
  this.memory = memory;
  this.SN = SN;
}
var phones = [];
for (var i = 0; i < 1000000; i++) {
  var memory = i % 2 == 0 ? 16 : 32;
  phones.push(new Iphone("iphone6s", 5.0, memory, i));
}

这段代码中,创建了一百万个iphone,每个iphone都独立申请一个内存。但是我们仔细观察可以看到,大部分iphone都是类似的,只是内存和序列号不一样,如果是一个对性能要求比较高的程序,我们就要考虑去优化它。
大量相似对象的程序,我们就可以考虑用享元模式去优化它,我们分析出大部分的iphone的型号,屏幕,内存都是一样的,那这部分数据就可以公用,就是享元模式中的内在数据,定义享元类如下:

function IphoneFlyweight(model, screen, memory) {
  this.model = model;
  this.screen = screen;
  this.memory = memory;
}

我们定义了iphone的享元类,其中包含型号,屏幕和内存三个数据。我们还需要一个享元工厂来维护这些数据:

var flyweightFactory = (function () {
  var iphones = {};
  return {
    get: function (model, screen, memory) {
      var key = model + screen + memory;
      if (!iphones[key]) {
        iphones[key] = new IphoneFlyweight(model, screen, memory);
      }
      return iphones[key];
    }
  };
})();

在这个工厂中,我们定义了一个字典来保存享元对象,提供一个方法根据参数来获取享元对象,如果字典中有则直接返回,没有则创建一个返回。
接着我们创建一个客户端类,这个客户端类就是修改自iphone类:

function Iphone(model, screen, memory, SN) {
  this.flyweight = flyweightFactory.get(model, screen, memory);
  this.SN = SN;
}

然后我们依旧像之间那样生成多个iphone

var phones = [];
for (var i = 0; i < 1000000; i++) {
  var memory = i % 2 == 0 ? 16 : 32;
  phones.push(new Iphone("iphone6s", 5.0, memory, i));
}
console.log(phones);

这里的关键就在于Iphone构造函数里面的this.flyweight = flyweightFactory.get(model, screen, memory)。这句代码通过享元工厂去获取享元数据,而在享元工厂里面,如果已经存在相同数据的对象则会直接返回对象,多个iphone对象共享这部分相同的数据,所以原本类似的数据已经大大减少,减少的内存的占用。

享元模式在DOM中的应用

享元模式的一个典型应用就是DOM事件操作,DOM事件机制分成事件冒泡和事件捕获。我们简单介绍一下这两者:
事件冒泡:绑定的事件从最里层的元素开始触发,然后冒泡到最外层
事件捕获:绑定的事件从最外层的元素开始触发,然后传到最里层
假设我们HTML中有一个菜单列表

<ul class="menu">
  <li class="item">选项1</li>
  <li class="item">选项2</li>
  <li class="item">选项3</li>
  <li class="item">选项4</li>
  <li class="item">选项5</li>
  <li class="item">选项6</li>
</ul>

点击菜单项,进行相应的操作,我们通过jQuery来绑定事件,一般会这么做:

$(".item").on("click", function () {
  console.log($(this).text());
})

给每个列表项绑定事件,点击输出相应的文本。这样看暂时没有什么问题,但是如果是一个很长的列表,尤其是在移动端特别长的列表时,就会有性能问题,因为每个项都绑定了事件,都占用了内存。但是这些事件处理程序其实都是很类似的,我们就要对其优化。

$(".menu").on("click", ".item", function () {
  console.log($(this).text());
})

通过这种方式进行事件绑定,可以减少事件处理程序的数量,这种方式叫做事件委托,也是运用了享元模式的原理。事件处理程序是公用的内在部分,每个菜单项各自的文本就是外在部分。我们简单说下事件委托的原理:点击菜单项,事件会从li元素冒泡到ul元素,我们绑定事件到ul上,实际上就绑定了一个事件,然后通过事件参数event里面的target来判断点击的具体是哪一个元素,比如低级第一个li元素,event.target就是li,这样就能拿到具体的点击元素了,就可以根据不同元素进行不同的处理。

总结

享元模式是一种优化程序性能的手段,通过共享公用数据来减少对象数量以达到优化程序的手段。享元模式适用于拥有大量类似对象并且对性能有要求的场景。因为享元模式需要分离内部和外部数据,增加了程序的逻辑复杂性,建议对性能有要求的时候才使用享元模式。

享元模式之利:
可以把网页的资源符合降低几个数量级。即使享元模式的应用无法将实例的个数削减到一个,你仍能够从中获益不少。

这种节省不需要大量修改原有代码。在创建了管理器、工厂和享元之后,就需要对代码进行的修改只不过是从直接实例化目标类改为调用管理器对象的某个方法。

享元模式之弊:
如果把它用在不必要的地方,其结果反而有损代码的运行效率。这种模式在优化代码的同时,也提高了其复杂程度,这会给调试和维护造成困难。

它之所以会妨碍调试,是因为现在可能出错的地方变成了三个:管理器、工厂和享元。

这种优化也会使维护变得更加困难。现在你面对的不是由封装着数据的对象构成的清晰架构,而是一堆又碎又乱的东西。其中的数据至少分两处保存。最好注释标明内在数据和外在数据。

只有在必要的时候才应该进行这种优化。必须在运行效率和可维护性之间进行权衡。如果拿不准是否需要使用享元模式,那么你很可能并不需要它。享元模式适合的是系统资源已经用得差不多而且明显需要进行某种优化这样一类场合。

这种模式对Javascript程序员特别有用,因为它可以用来减少网页上所要使用的DOM元素的数量,要知道这些元素需要耗费许多内存。结合使用这种模式与组合模式等组织型可以开发出功能丰富的复杂Web应用系统,它们可以平稳的运行在任何现代Javascript环境中。

享元模式的适用场合:
网页中必须使用了大量资源密集型对象。如果只会用到少许这类对象,这种优化并不划算。

对象中所保存的数据至少有一部分能被转化为外在数据。此外,将这些数据存储在对象外部所占用的资源应该相对较少,否则这种做法对于性能的提示实际上毫无意义。那种大量包含基础性代码和HTML内容的对象可能比较适合这种优化。

将外在数据分离出去后,独一无二的对象的数目相对较少。

Javascript 相关文章推荐
JavaScript实现使用Canvas绘制图形的基本教程
Oct 27 Javascript
javascript实现鼠标点击页面 移动DIV
Dec 02 Javascript
vue双向数据绑定原理探究(附demo)
Jan 17 Javascript
Vue 实用分页paging实例代码
Apr 12 Javascript
js模拟百度模糊搜索的实例
Aug 04 Javascript
利用vue.js实现被选中状态的改变方法
Feb 08 Javascript
小程序视频或音频自定义可拖拽进度条的示例代码
Sep 30 Javascript
node app 打包工具pkg的具体使用
Jan 17 Javascript
Vue实现购物车的全选、单选、显示商品价格代码实例
May 06 Javascript
vue实现动态表格提交参数动态生成控件的操作
Nov 09 Javascript
vue实现按钮切换图片
Jan 20 Vue.js
Ajax 的初步实现(使用vscode+node.js+express框架)
Jun 18 Javascript
在JavaScript中模拟类(class)及类的继承关系
May 20 #Javascript
基于Node.js的JavaScript项目构建工具gulp的使用教程
May 20 #Javascript
JavaScript中用let语句声明作用域的用法讲解
May 20 #Javascript
如何用JavaScript实现动态修改CSS样式表
May 20 #Javascript
JavaScript中的跨浏览器事件操作的基本方法整理
May 20 #Javascript
用JavaScript动态建立或增加CSS样式表的实现方法
May 20 #Javascript
详解JavaScript中的事件流和事件处理程序
May 20 #Javascript
You might like
php打印一个边长为N的实心和空心菱型的方法
2015/03/02 PHP
PHP实现微信小程序用户授权的工具类示例
2019/03/05 PHP
在Laravel中实现使用AJAX动态刷新部分页面
2019/10/15 PHP
PHP使用openssl扩展实现加解密方法示例
2020/02/20 PHP
JQuery的Validation插件中Remote验证的中文问题
2010/07/26 Javascript
基于jquery的一个浮动框(扩展性比较好 )
2010/08/27 Javascript
基于Jquery的动态添加控件并取值的实现代码
2010/09/24 Javascript
JS中如何判断传过来的JSON数据中是否存在某字段
2014/08/18 Javascript
javascript随机显示背景图片的方法
2015/06/18 Javascript
浅谈javascript中的constructor
2016/06/08 Javascript
详解angular中的作用域及继承
2017/05/31 Javascript
vue 使用vue-i18n做全局中英文切换的方法
2018/10/29 Javascript
NodeJs操作MongoDB教程之分页功能以及常见问题
2019/04/09 NodeJs
基于JS判断对象是否是数组
2020/01/10 Javascript
JavaScript 替换所有匹配内容及正则替换方法
2020/02/12 Javascript
小程序自定义导航栏兼容适配所有机型(附完整案例)
2020/04/26 Javascript
Vue实现PC端靠边悬浮球的代码
2020/05/09 Javascript
[01:32]2016国际邀请赛中国区预选赛CDEC战队教练采访
2016/06/26 DOTA
天翼开放平台免费短信验证码接口使用实例
2013/12/18 Python
Python编程之字符串模板(Template)用法实例分析
2017/07/22 Python
python 每天如何定时启动爬虫任务(实现方法分享)
2018/05/21 Python
利用Python实现原创工具的Logo与Help
2018/12/03 Python
Django 中自定义 Admin 样式与功能的实现方法
2019/07/04 Python
python按比例随机切分数据的实现
2019/07/11 Python
SQL注入攻击的种类有哪些
2013/12/30 面试题
如果重写了对象的equals()方法,需要考虑什么
2014/11/02 面试题
材料加工硕士生求职信
2013/10/10 职场文书
差生评语大全
2014/05/04 职场文书
应用心理学专业求职信
2014/08/04 职场文书
小王子读书笔记
2015/06/29 职场文书
给校长的建议书作文300字
2015/09/14 职场文书
标会主持词应该怎么写?
2019/08/15 职场文书
python自动化调用百度api解决验证码
2021/04/13 Python
Python使用海龟绘图实现贪吃蛇游戏
2021/06/18 Python
Java 在线考试云平台的实现
2021/11/23 Java/Android
uniapp 微信小程序 自定义tabBar 导航
2022/04/22 Javascript