javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JavaScript 享元模式

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

作者:liangyue

享元模式是一种用于性能优化的模式。享元模式的核心是运用共享技术来有效支持大量细粒度的对象.如果系统中创建了大量类似的对象而导致内存占用过高,本文通过介绍书中文件上传的优化案例来说明享元模式的使用方式和作用,需要的朋友可以参考下

概念

在《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属性才可以工作,只有在创建的时候明确了组件的类型,才可以调用各自的方法,而fileNamefileSize 是根据场景而变化的,不能被共享。因此,我们可以确定:uploadType属性为内部状态,而fileNamefileSize为外部状态

根据上面的分析,我们首先把外部状态剥离出来,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 享元模式的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文