Javascript享元模式

Javascript享元模式

  • 1 什么是享元模式
  • 2 内部状态与外部状态
  • 3 享元模式的通用结构
  • 4 文件上传
    • 4.1 对象爆炸
    • 4.2 享元模式重构
  • 5 没有内部状态的享元模式
  • 6 对象池
  • 7 通用对象池实现

1 什么是享元模式

享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级。

享元模式的核心是运用共享技术来有效支持大量细粒度的对象。如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。

假设服装店新到了50套男士衣服和50套女士衣服,为了推销出去,店里决定买一些模特来穿上衣服进行宣传。一般情况下,需要50个男模特和50个女模特,每个模特穿上衣服拍照,实现代码如下:

var Model = function (sex, underwear) {
  this.sex = sex;
  this.underwear = underwear;
};

Model.prototype.takePhote = function () {
  console.log("sex=" + this.sex + "underwear=" + this.underwear);
};

for (let i = 1; i <= 50; i++) {
  var maleModel = new Model("male", "underwear" + i);
  maleModel.takePhote();
}

for (let j = 1; j <= 50; j++) {
  var femaleModel = new Model("female", "underwear" + j);
  femaleModel.takePhote();
}

如果要得到一张照片,每次都需要传入sexunderwear参数,如上所示,现在一共有50套男款服装和50套女款服装,所以一共会产生100个对象。如果之后有10000套衣服,那这个程序可能会因为存在如此多的对象已经提前崩溃。

其实我们可以想到,虽然有100套衣服,但很显然并不需要50个男模特和50个女模特,男模特和女模特各自有一个就足够了,他们可以分别穿上不同的衣服来拍照。现在我们根据以上逻辑改写一下代码:

var Model = function (sex) {
  this.sex = sex;
};

Model.prototype.takePhote = function () {
  console.log("sex=" + this.sex + ", underwear=" + this.underwear);
};

// 首先分别创建一个男模特和一个女模特
var maleModel = new Model("male");
var femaleModel = new Model("female");

// 依次让男模特穿上所有的男装拍照
for (let i = 1; i <= 50; i++) {
  maleModel.underwear = "underwear" + i;
  maleModel.takePhote();
}

// 依次让女模特穿上所有的女装拍照
for (let j = 1; j <= 50; j++) {
  femaleModel.underwear = "underwear" + j;
  femaleModel.takePhote();
}

我们可以看到,改进之后的代码,只有两个对象,就可以完成同样的拍照的任务。

2 内部状态与外部状态

上面的这个例子便是享元模式的雏形,享元模式要求将对象的属性划分为内部状态外部状态(状态在这里通常指属性)。享元模式的目标是尽量减少共享对象的数量

那么如何区分内部状态和外部状态呢,我们可以根据下面这几条特征来区分:

  • 内部状态存储于对象内部
  • 内部状态可以被一些对象共享
  • 内部状态独立于具体的场景,通常不会改变
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享

这样一来,我们便可以把所有内部状态相同的对象都指定为同一个共享的对象。而外部状态可以从对象身上剥离出来,并储存在外部。

剥离了外部状态的对象成为共享对象,外部状态在必要时被传入共享对象来组装成一个完整的对象。虽然组装外部状态成为一个完整对象的过程需要花费一定的时间,但却可以大大减少系统中的对象数量。因此,享元模式是一种用时间换空间的优化模式。

在上面的例子中,性别是内部状态,衣服是外部状态,通过区分这两种状态,大大减少了系统中的对象数量。

3 享元模式的通用结构

上面展示的例子还不是一个完整的享元模式,在这个例子中还存在以下两个问题:

  • 我们通过构造函数显式new出了男女两个model对象,在其他系统中,也许并不是一开始就需要所有的共享对象
  • model对象手动设置了underwear外部状态,在更复杂的系统中,这不是一个最好的方式,因为外部状态可能会相当复杂,它们与共享对象的联系会变得困难。

我们通过一个对象工厂来解决第一个问题,只有当某种共享对象被真正需要时,它才从工厂中被创建出来。对于第二个问题,可以用一个管理器来记录对象相关的外部状态,使这些外部状态通过某个钩子和共享对象联系起来。

4 文件上传

4.1 对象爆炸

什么是对象爆炸呢,比如说在文件上传功能中,可以选择依照队列一个一个地排队上传,也可以同时选择2000个文件。每一个文件都对应着一个JavaScript上传对象的创建,那么同时上传2000个文件,程序中就需要同时new2000个upload对象,这对浏览器会造成很大的冲击。

比如我们要实现使用插件或者Flash上传文件的功能,当用户选择了需要上传的文件之后,它们会去通知调用Window下的一个全局Javascript函数startUpload,用户选择的文件列表被组合成一个数组files塞进该函数的参数列表种,代码如下:

var id = 0;

window.startUpload = function (uploadType, files) {
  for (let i = 0, file; (file = files[i++]); ) {
    var uploadObj = new upload(uploadType, file.fileName, file.fileSize);
    uploadObj.init(id++); // 为upload对象设置一个唯一的id
  }
};

当用户选择完文件之后,startUpload函数会遍历files数组来创建对应的upload对象。接下来定义Upload构造函数,它接受3个参数,分别是插件类型、文件名和文件大小。这些信息都已经被插件组装在files数组里返回,代码如下:

var Upload = function (uploadType, fileName, fileSize) {
  this.uploadType = uploadType;
  this.fileName = fileName;
  this.fileSize = fileSize;
  this.dom = null;
};

Upload.prototype.init = function (id) {
  var that = this;
  this.id = id;
  this.dom = document.createElement("div");
  this.dom.innerHTML =
    "<span>文件名称:" +
    this.fileName +
    ", 文件大小: " +
    this.fileSize +
    "</span>" +
    '<button class="delFile">删除</button>';
  this.dom.querySelector(".delFile").onclick = function () {
    that.delFile();
  };
  document.body.appendChild(this.dom);
};

假设upload对象只有删除文件的功能,对应的方法是Upload.prototype.delFile。该方法中有一个逻辑:当被删除的文件小于3000KB时,该文件将被直接删除,否则页面中会弹出一个提示框,提示用户是否确认要删除该文件,代码如下:

Upload.prototype.delFile = function () {
  if (this.fileSize < 3000) {
    return this.dom.parentNode.removeChild(this.dom);
  }
  if (window.confirm("确定要删除该文件吗? " + this.fileName)) {
    return this.dom.parentNode.removeChild(this.dom);
  }
};

接下来分别创建3个插件上传对象和3 个Flash上传对象:

startUpload("plugin", [
  { fileName: "1.txt", fileSize: 1000 },
  { fileName: "2.html", fileSize: 3000 },
  { fileName: "3.txt", fileSize: 5000 },
]);

startUpload("flash", [
  { fileName: "4.txt", fileSize: 1000 },
  { fileName: "5.html", fileSize: 3000 },
  { fileName: "6.txt", fileSize: 5000 },
]);

在这里插入图片描述

4.2 享元模式重构

首先确认内部状态和外部状态,在上面的例子中,只有上传类型uploadType是内部状态,在文件上传的例子里,upload对象必须依赖uploadType属性才能工作,这是因为插件上传、Flash上传、表单上传的实际工作原理有很大的区别,它们各自调用的接口也是完全不一样的,必须在对象创建之初就明确它是什么类型的插件,才可以在程序的运行过程中,让它们分别调用各自的方法。

无论我们使用什么方式上传,这个上传对象都是可以被任何文件共用的。而fileNamefileSize是根据场景而变化的,每个文件的fileNamefileSize都不一样, 它们只能被划分为外部状态。

明确了uploadType作为内部状态之后,我们再把其他的外部状态从构造函数中抽离出来,Upload构造函数中只保留uploadType参数:

var Upload = function (uploadType) {
  this.uploadType = uploadType;
};

Upload.prototype.init函数也不再需要,因为upload对象初始化的工作被放在了uploadManager.setExternalState函数里面,接下来只需要定义Upload.prototype.del函数即可:

// 剥离外部状态
Upload.prototype.delFile = function (id) {
  uploadManager.setExternalState(id, this); // 将id对应的对象的外部状态组装到共享对象中
  if (this.fileSize < 3000) {
    return this.dom.parentNode.removeChild(this.dom);
  }
  if (window.confirm("确定要删除该文件吗? " + this.fileName)) {
    return this.dom.parentNode.removeChild(this.dom);
  }
};

接下来定义一个工厂来创建upload对象,如果某种内部状态对应的共享对象已经被创建过,那么直接返回这个对象,否则创建一个新的对象:

// 使用工厂模式进行对象实例化
var UploadFactory = (function () {
  var createdFlyWeightObjs = {};
  return {
    create: function (uploadType) {
      if (createdFlyWeightObjs[uploadType]) {
        return createdFlyWeightObjs[uploadType];
      }
      return (createdFlyWeightObjs[uploadType] = new Upload(uploadType));
    },
  };
})();

现在我们来完善uploadManager对象,它负责向UploadFactory提交创建对象的请求,并用一个 uploadDatabase对象保存所有upload对象的外部状态,以便在程序运行过程中给upload共享对象设置外部状态,代码如下:

// 使用管理器封装外部状态
var uploadManager = (function () {
  var uploadDatabase = {};
  return {
    add: function (id, uploadType, fileName, fileSize) {
      var flyWeightObj = UploadFactory.create(uploadType);
      var dom = document.createElement("div");
      dom.innerHTML =
        "<span>文件名称:" +
        fileName +
        ", 文件大小: " +
        fileSize +
        "</span>" +
        '<button class="delFile">删除</button>';
      dom.querySelector(".delFile").onclick = function () {
        flyWeightObj.delFile(id);
      };
      document.body.appendChild(dom);
      uploadDatabase[id] = {
        fileName: fileName,
        fileSize: fileSize,
        dom: dom,
      };
      return flyWeightObj;
    },
    setExternalState: function (id, flyWeightObj) {
      var uploadData = uploadDatabase[id];
      for (var i in uploadData) {
        flyWeightObj[i] = uploadData[i];
      }
    },
  };
})();

触发上传动作:

var id = 0;
window.startUpload = function (uploadType, files) {
  for (var i = 0, file; (file = files[i++]); ) {
    uploadManager.add(++id, uploadType, file.fileName, file.fileSize);
  }
};

测试一下:

startUpload("plugin", [
  { fileName: "1.txt", fileSize: 1000 },
  { fileName: "2.html", fileSize: 3000 },
  { fileName: "3.txt", fileSize: 5000 },
]);

startUpload("flash", [
  { fileName: "4.txt", fileSize: 1000 },
  { fileName: "5.html", fileSize: 3000 },
  { fileName: "6.txt", fileSize: 5000 },
]);

享元模式重构之前的代码里一共创建了6个upload对象,而通过享元模式重构之后,对象的数量减少为2,更幸运的是, 就算现在同时上传2000个文件,需要创建的upload对象数量依然是2。

5 没有内部状态的享元模式

在文件上传的例子中,我们分别进行过插件调用和Flash调用,导致程序中创建了内部状态不同的两个共享对象。但是在文件上传程序里,一般都会提前通过特性检测来选择一种上传方式,如果浏览器支持插件就用插件上传,如果不支持插件,就用Flash上传。

那么这种情况下,之前作为内部状态存在的uploadType属性是可以删掉的,在继续使用享元模式的前提下,构造函数Upload就变成了无参数的形式:

var Upload = function () {};

其他属性如依然可以作为外部状态保存在共享对象外部,改写创建享元对象的工厂,代码如下:

// 使用工厂模式进行对象实例化
var UploadFactory = (function () {
  var uploadObj;
  return {
    create: function () {
      if (uploadObj) {
        return uploadObj;
      }
      return (uploadObj = new Upload());
    },
  };
})();

管理器部分的代码不需要改动,还是负责剥离和组装外部状态。可以看到,当对象没有内部状态的时候,生产共享对象的工厂实际上变成了一个单例工厂。虽然这时候的共享对象没有内部状态的区分,但还是有剥离外部状态的过程,我们依然倾向于称之为享元模式。

6 对象池

对象池维护一个装载空闲对象的池子,如果需要对象的时候,不是直接new,而是转从对象池里获取。如果对象池里没有空闲对象,则创建一个新的对象,当获取出的对象完成它的职责之后, 再进入
池子等待被下次获取。

对象池技术的应用非常广泛,HTTP连接池和数据库连接池都是其代表应用。在Web前端开发中,对象池使用最多的场景大概就是跟DOM有关的操作。很多空间和时间都消耗在了DOM节点上,如何避免频繁地创建和删除DOM节点就成了一个有意义的话题。

比如说,在地图软件中,经常会出现一些标志地名的小气泡,如下所示:
请添加图片描述
当我搜索故宫时,出现了3个小气泡,当我搜索王府井时,出现了5个气泡,按照对象池的思想,在第二次搜索开始之前,并不会把第一次创建的3个小气泡删除掉,而是把它们放进对象池。这样在第二次的搜索结果页面里,我们只需要再创建2个小气泡而不是5个。
请添加图片描述
先定义一个获取小气泡节点的工厂,作为对象池的数组成为私有属性被包含在工厂闭包里,这个工厂有两个暴露对外的方法,create表示获取一个div节点,recover表示回收一个div节点:

var toolTipFactory = (function () {
  var toolTipPool = []; // toolTip 对象池
  return {
    create: function () {
      // 如果对象池为空
      if (toolTipPool.length === 0) {
        var div = document.createElement("div"); // 创建一个 dom
        document.body.appendChild(div);
        return div;
      } else {
        // 如果对象池里不为空
        return toolTipPool.shift(); // 则从对象池中取出一个 dom
      }
    },
    recover: function (tooltipDom) {
      return toolTipPool.push(tooltipDom); // 对象池回收 dom
    },
  };
})();

第一次搜索时,需要创建3个小气泡节点,为了方便回收,用一个数组ary记录它们:

var ary = [];
for (var i = 0, str; (str = ["A", "B", "C"][i++]); ) {
  var toolTip = toolTipFactory.create();
  toolTip.innerHTML = str;
  ary.push(toolTip);
}

在这里插入图片描述
接下来假设地图需要开始重新绘制,在此之前要把这3个节点回收进对象池:

for (var i = 0, toolTip; (toolTip = ary[i++]); ) {
  toolTipFactory.recover(toolTip);
}

再创建5个小气泡:

for (var i = 0, str; (str = ["A", "B", "C", "D", "E"][i++]); ) {
  var toolTip = toolTipFactory.create();
  toolTip.innerHTML = str;
}

在这里插入图片描述
现在再测试一番,页面中出现了5个节点,上一次创建好的节点被共享给了下一次操作。对象池跟享元模式的思想有点相似,虽然innerHTML的值也可以看成节点的外部状态,但在这里我们并没有主动分离内部状态和外部状态的过程。

7 通用对象池实现

我们还可以在对象池工厂里,把创建对象的具体过程封装起来,实现一个通用的对象池:

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 重复加载
    iframeFactory.recover(iframe); // iframe 加载完成之后回收节点
  };
  return iframe;
});

var iframe1 = iframeFactory.create();
iframe1.src = "http:// baidu.com";

var iframe2 = iframeFactory.create();
iframe2.src = "http:// QQ.com";

setTimeout(function () {
  var iframe3 = iframeFactory.create();
  iframe3.src = "http:// 163.com";
}, 3000);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/138256.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据恢复工具推荐,高效恢复,这4款很实用!

很多电脑用户都会选择将文件直接保存在电脑上&#xff0c;但是在实际的操作过程中&#xff0c;数据丢失的情况难免会出现。而实用的数据恢复工具或许能有效帮助我们找回丢失的数据。电脑上有哪些使用效果比较好的数据恢复工具呢&#xff1f; 今天小编总结了几款好用的工具&…

leetcode:21. 合并两个有序链表

一、题目 函数原型&#xff1a; struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 二、思路 合并两个有序链表为一个新的升序链表&#xff0c;只需要遍历两个有序链表并比较结点值大小&#xff0c;依次将较小的结点尾插到新链表即可。 三、代码…

C#中.NET Framework 4.8控制台应用通过EF访问已建数据库

目录 一、创建.NET Framework 4.8控制台应用 二、建立数据库 1. 在SSMS中建立数据库Blogging 2.在VS上新建数据库连接 三、安装EF程序包 四、自动生成EF模型和上下文 1.Blog.cs类的模型 2.Post.cs类的模型 3.BloggingContext.cs数据库上下文 五、编写应用程序吧 我们…

Vatee万腾数字化引领未来,vatee创新思维

随着数字化时代的全面来临&#xff0c;Vatee万腾正以其独特的创新思维&#xff0c;为未来描绘出令人瞩目的数字化画卷。在这个充满变革和机遇的时代&#xff0c;Vatee万腾所展现的数字化引领力和创新思维&#xff0c;成为业界的翘楚。 Vatee万腾的创新思维贯穿于其数字化战略的…

数据结构 | 队列的实现

数据结构 | 队列的实现 文章目录 数据结构 | 队列的实现队列的概念及结构队列的实现队列的实现头文件&#xff0c;需要实现的接口 Queue.h初始化队列队尾入队列【重点】队头出队列【重点】获取队列头部元素获取队列队尾元素获取队列中有效元素个数检测队列是否为空销毁队列 Que…

更新:扶风解析计费系统V1.8.2源码/免授权优化版+附教程/修正完整版

源码简介&#xff1a; 最新的扶风解析计费系统V1.8.2源码&#xff0c;它是修正完整版&#xff0c;免授权优化版附带了教程。是更新优化版最新 V1.8 版本免授权版本。 之前分享过1.7.1版本的扶风计费系统&#xff0c;该版本已经存在相当长的时间&#xff0c;并且一直没有进行更…

一文读懂:什么是RISC-V?为啥它是国产芯崛起的关键?

各位ICT的小伙伴们大家好呀。 提到CPU&#xff0c; 大家首先就会想到"卡脖子"事件。 X86和ARM的IP授权虽然方便&#xff0c;但是不自主和不可控&#xff0c; 一被限制就可能导致国内一夜间"无芯"可用。 今天我们就来聊聊一个解决芯片卡脖子的有效方式-…

多路复用IO:select、poll、epoll

文章目录 一、常见的IO模型二、什么是多路IO复用&#xff1f;三、select、poll、epollselectpollepoll 四、总结 一、常见的IO模型 概念优点       缺点适用场景阻塞IOBlocking IO当应用程序执行IO操作时&#xff0c;会被阻塞&#xff0c;直到数据准备好或者IO操作完成才…

项目管理工具:提高团队协作效率,确保项目按时完成

项目管理对于企业的成功至关重要&#xff0c;一个好的项目管理工具可以提高团队协作效率&#xff0c;确保项目按时完成&#xff0c;并保持项目进度的高效跟踪。 近年来&#xff0c;一款名为“进度猫”的项目管理工具逐渐崭露头角&#xff0c;它以其独特的功能和优势&#xff…

删除快一年的数据,能够恢复吗?

在数字化时代&#xff0c;数据已经成为了企业和个人生活中不可或缺的一部分。然而&#xff0c;由于各种原因&#xff0c;我们有时会需要删除某些数据&#xff0c;比如过期的文件、无用的照片或者账号下的旧信息等。但是&#xff0c;当我们删除这些数据后&#xff0c;是否真的能…

提高生产效率和质量,这个方式很有效

在当今竞争激烈的市场环境下&#xff0c;企业需要不断提高生产效率和质量水平以保持竞争优势。而精益生产正是一种能够帮助企业实现这一目标的方法。其中&#xff0c;持续改善是精益生产的核心理念之一。它是指通过不断地寻找和消除浪费&#xff0c;改善流程和提高效率来实现质…

PHP中$_SERVER全局变量

在PHP中&#xff0c;$_SERVER 是一个全局数组变量&#xff0c;它包含了有关服务器和当前脚本的信息。$_SERVER 数组中的每个元素都是服务器环境的一个参数&#xff0c;如请求的方法、请求的 URI、客户端 IP 地址等。 PATH 系统环境变量的值&#xff0c;包含了多个目录的路径…

Xmind 24 for Mac思维导图软件

XMind是一款流行的思维导图软件&#xff0c;可以帮助用户创建各种类型的思维导图和概念图。 以下是XMind的主要特点&#xff1a; - 多样化的导图类型&#xff1a;XMind提供了多种类型的导图&#xff0c;如鱼骨图、树形图、机构图等&#xff0c;可以满足不同用户的需求。 - 强大…

通过 Kaptcha 插件生成字符验证码

Kaptcha 是 Google 的⼀个⾼度可配置的实⽤验证码⽣成⼯具&#xff0c;我们选择的是⼀个适配SpringBoot的 开源项⽬ 生成的验证码效果如下&#xff1a; 原理 验证码可以客户端生成,也可以服务器生成. 对于普通的字符验证码, 后端通常分两部分&#xff1a; ⼀&#xff1a;⽣成验…

能跟“猫主子”聊天了!生成式AI最快5年内破译第一种动物语言

image.png ChatGPT用它自己的方式来理解世界&#xff0c;类似的技术是否也能用来学习动物的语言&#xff1f; 所罗门能够与动物交流并不是因为他拥有魔法物品&#xff0c;而是因为他有观察的天赋。 ——康拉德・劳伦兹《所罗门王的指环》 在《狮子王》、《疯狂动物城》等以动…

“颠覆·挑战·极致”华瑞指数云ExponTech WDS新一代产品重新定义企业存储和数据架构

数字经济发展&#xff0c;离不开数据这一信息时代的“新能源”。当数据爆发式增长&#xff0c;企业何处寻得一款在性能和成本上皆具备良好表现的“储能仓”&#xff1f;国内数据存储领域领先厂商华瑞指数云ExponTech自主研发的高性能、高可靠的分布式存储产品ExponTech WDS成为…

SCADA系统在化工行业应用解决方案和注意事项

SCADA系统在化工行业的数字化工厂中具有广泛的应用解决方案。SCADA系统通过实时监控和远程控制&#xff0c;帮助化工企业实现生产过程的自动化和数字化管理。以下是化工行业的SCADA系统行业应用中可以解决的客户痛点以及相关的详细设计说明&#xff1a; 远程监测和控制&#xf…

【VECTOR】:CAN OE Alyzer使用

CAN OE Alyzer使用 工程搭建新建工程DBC文件导入插入IG模块Trace查看录制Logger回放Trace 实际应用将需要回放报文的导出需要报文添加导出的报文&#xff0c;回放 工程搭建 新建工程 配置硬件1&#xff1a;通道数量选择&#xff08;根据使用情况而定&#xff09; 硬件配置2&am…

从0到0.01入门React | 006.精选 React 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…