详解JavaScript设计模式中的享元模式

作者:青柠

时间:2026-10-30

浏览:420

本文详解JavaScript中的享元模式,通过文件上传案例演示如何将对象属性划分为内部状态与外部状态。通过共享相同类型的对象实例,有效减少内存开销,解决大量相似对象导致的性能问题,提升浏览器运行效率。

文章目录

概念

在《JavaScript设计模式与开发实践》中,享元模式被定义为一种旨在优化程序性能的设计模式。其核心机制在于利用共享技术,从而高效地支持数量庞大且粒度细腻的众多对象。

当系统中创建了大量相似对象并导致内存占用过高时,应用享元模式尤为有效。该模式的主要目标是尽可能减少共享对象的总数量,以缓解资源压力。

使用享元模式

应用享元模式的关键在于将对象的属性明确划分为内部状态与外部状态。关于如何准确划分这两类状态,可参考以下经验准则:

  • 内部状态必须存储于对象内部,由对象自身持有。
  • 内部状态具备可共享性,可以被多个不同的对象实例共同使用。
  • 内部状态通常独立于具体的运行场景,在对象生命周期内一般保持不变。
  • 外部状态则依赖于具体的应用场景,会随场景变化而改变,且外部状态不能被多个对象共享。

优化文件上传

本节将通过书中提供的文件上传优化案例,具体说明享元模式的应用方式及其实际作用。在该案例中,作者借助享元模式显著提升了程序性能,下面将详细解析该示例。

常规方式

在开发上传功能时,面临对象数量激增的问题。该上传功能既支持按队列逐个上传,也支持一次性选择2000个文件进行并发上传。在初始版本开发中,程序同时new了2000个upload对象,导致在IE浏览器环境下程序直接陷入假死状态。此处先展示简化后的伪代码:

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

接下来,我们将展示上传组件的初始化代码,具体如下:

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

在上述代码中,uploadType参数支持多种不同的上传模式,例如浏览器插件、Flash以及表单上传等。当用户选定文件后,调用window下的startUpload函数,该函数内部会创建Upload对象。实际上,Upload对象的体积相当庞大。采用这种方式,意味着有多少个文件就需要创建多少个Upload对象,接下来我们将使用享元模式对代码进行优化。

享元模式重构

首先,我们需要明确Upload对象属性中哪些属于内部状态,哪些属于外部状态。

Upload对象必须依赖uploadType属性才能正常工作,只有在创建时明确了组件类型,才能调用相应的方法;而fileName和fileSize会根据具体场景发生变化,无法被共享。因此,我们可以确定:uploadType属性属于内部状态,而fileName和fileSize属于外部状态。

基于上述分析,我们首先将外部状态剥离,Upload函数中仅保留uploadType参数:

var Upload = function (uploadType) {
  this.uploadType = uploadType;
}
Upload.prototype.delFile = function (id) {
  uploadManager.setExternalState(id, this);
  return this.dom.parentNode.removeChild(this.dom);
}

那么,在实际使用中应如何操作呢?之前我们可以通过init方法创建一个上传组件,接下来,我们需要创建两个关键对象:UploadFactory用于创建Upload对象,uploadManager用于管理上传对象(包括添加上传组件以及为Upload设置正确的处理文件),代码如下:

var UploadFactory = (function () {
  var createdFlyWeightObjs = {};
  return {
    create: function (uploadType) {
      if (createdFlyWeightObjs[uploadType]) {
        return createdFlyWeightObjs[uploadType];
      }
      return createdFlyWeightObjs[uploadType] = new Upload(uploadType);
    }
  }
})()
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);
      }
      // 添加上传组件
      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];
      }
    }
  }
})()

代码分析

UploadFactory:从代码逻辑不难理解,UploadFactory中包含一个create方法,该方法用于创建Upload对象并将其存储到createdFlyWeightObjs中;对于相同的uploadType,我们也只会创建一个Upload实例。

uploadManager:该对象将不同文件的fileName、fileSize等属性保存到uploadDatabase中,当执行setExternalStates时,会将正确的文件对象设置到flyWeightObj;在执行add时,我们会创建Upload对象,并将上传文件信息保存到uploadDatabase中。

因此,当我们需要执行文件上传操作时,需要相应地修改window.startUpload,具体代码如下:

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

通过ploadManager.add进行创建时,我们不再根据文件数量创建多个Upload对象,相同的uploadType会共享同一个Upload实例。当我们执行某个文件的删除操作时:

我们会先通过uploadManager.setExternalState方法设置需要操作的文件,包括fileName、fileSize、dom,然后执行相关的删除逻辑。例如:代码中是将当前的dom删除,即可以直接使用this.dom。

使用享元模式重构后,可以大量减少创建Upload对象的数量,从而实现性能优化的目标。

总结

享元模式是为了解决性能问题而诞生的设计模式。在上述示例中,我们通过享元模式减少了对象的创建次数,从而提升了浏览器的运行性能。然而,在许多开源代码中,我尚未发现使用享元模式的真实案例。

但享元模式的思想确实随处可见,例如vue、react等框架在处理DOM元素更新时,都会通过diff算法来实现DOM节点的复用,从而提升一定的性能。

享元模式的适用性

最后,列出书中总结的该模式的适用场景,当出现以下情况时,便可以考虑使用享元模式:

  • 一个程序使用了大量的相似对象。
  • 由于使用了大量对象,造成了很大的内存开销。
  • 对象的大多数状态都可以变为外部状态。
  • 剥离出对象的外部状态后,可以使用相对较少的共享对象取代大量对象。

以上就是详解JavaScript设计模式中的享元模式的详细内容,更多关于JavaScript 享元模式的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章