前端知识总结js设计模式

js常用设计模式7-享元模式

2021-01-18  本文已影响0人  青色琉璃

js常用设计模式1-单例模式
js常用设计模式2-策略模式
js常用设计模式3-代理模式
js常用设计模式4-发布-订阅模式
js常用设计模式5-命令模式
js常用设计模式6-组合模式
js常用设计模式7-享元模式
js常用设计模式8-职责链模式
js常用设计模式9-中介者模式
js常用设计模式10-装饰者模式
js常用设计模式11-状态模式

享元模式是一种用于性能优化的模式,核心是运用共享技术来有效支持大量细粒度的对象。
如果系统中创建了大量类似的对象,导致内存占用过高,这时候享元模式就很有用了。

1,假发工厂

假设有一个假发工厂,生产50种男士假发和50种女士假发,这时候我们需要50个男模和50个女模,分别套上假发,然后拍照:

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

Model.prototype.takePhoto = function () {
  console.log('sex:' + this.sex + 'underware:' + this.underware)
}

for (var i = 0; i < 50; i++) {
  var maleModel = new Model('男', 'underware' + i)
  maleModel.takePhoto()
}
for (var i = 0; i < 50; i++) {
  var femaleModel = new Model('女', 'underware' + i)
  femaleModel.takePhoto()
}

很明显,这种做法略显沙雕。

2,享元模式优化

实际上,我们只需要两个模特就够了,假发换着套就行。

//效果一样,感觉好多了
var Model = function (sex) {
  this.sex = sex
}
Model.prototype.takePhoto = function () {
  console.log('sex:' + this.sex + 'underware:' + this.underware)
}

var maleModel = new Model('男')
var femaleModel = new Model('女')

for (var i = 0; i < 50; i++) {
  maleModel.underware = 'underware' + i
  maleModel.takePhoto()
}

for (var i = 0; i < 50; i++) {
  femaleModel.underware = 'underware' + i
  femaleModel.takePhoto()
}

3,内部状态与外部状态

享元模式的目标是尽量减少共享对象的数量:
(1)内部状态存储于对象内部
(2)内部状态可以被一些对象共享
(3)内部状态独立于具体的场景
(4)外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享
享元模式是一种时间换空间的优化模式

4,普通版本上传文件的例子

现在我们要实现一个功能:往服务器上传文件。假设目前只有两种上传方式:插件上传和flash上传。这俩实现是一样的,选择文件之后,都会调用window下的startUpload方法,用户选择的文件列表被组合成一个数组files放到该函数的参数列表中。
代码如下:

  var id = 0;
  //uploadType区分是插件上传还是flash
    window.startUpload = function (uploadType, files) {
      for (var i = 0, file; file = files[i++];) {
        var upload = new Upload(uploadType, file.fileName, file.fileSize)
        upload.init(id++)
      }
    }

用户选择完文件后,startUpload函数会遍历files数组,挨个创建对应的upload对象。现在我们要定义Upload构造函数,除了初始化init函数之外,我们还需要一个删除函数delFile。代码如下:

// uploadType:上传类型, fileName:文件名, fileSize:文件大小
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.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)
  }
}

现在,让我们来try一try:

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

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

5,享元模式优化文件上传

上面这个上传功能,有多少个需要上传的对象,就创建了多少个upload对象,非常的浪费。下面,我们用享元模式来重构它。
(1)首先,要明确内部状态
upload对象依赖于upLoadType属性,由于我们现在是简化版的上传,因此只要明确了upLoadType,这个upload对象是可以被其他文件共享的。而fileName、fileSize都不一样,因此这俩是外部状态。
(2)剥离外部状态
现在,我们的Upload构造函数中,只需要upLoadType这一个参数就可以:

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

Upload.prototype.init函数也不需要了,现在upload对象的初始化被放在了uploadManager.add这个函数中。
接下来我们只需要定义Upload.prototype.delFile函数就ok:

  Upload.prototype.delFile = function (id) {
      // 删除之前,读取文件实际大小
      uploadManager.setExternalState(id, this)

      if (this.fileSize < 3000) {
        return this.dom.parentNode.removeChild(this.dom)
      }
      if (window.confirm('确定删除该文件吗?' + this.fileName)) {
        return this.dom.parentNode.removeChild(this.dom)
      }
    }

(3)工厂函数进行对象实例化
定义一个工厂,用来创建upload对象,如果某种内部状态(upLoadType)的对象已经被创建过,那么直接返回这个对象,否则创建新的对象:

   var UploadFactory = (function () {
      var createdFlyWeightObjs = {}

      return {
        create: function (uploadType) {
          if (createdFlyWeightObjs[uploadType]) {
            return createdFlyWeightObjs[uploadType]
          }
          return createdFlyWeightObjs[uploadType] = new Upload(uploadType)
        }
      }
    })()

(4)管理器封装外部状态
在(2)中我们提到了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]
          }
        }
      }
    })()

现在,我们要触发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)
      }
    }

让我们try一try最后的效果:

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

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

6,小结
享元模式是一个很好的性能优化方案,但是因为多了uploadManager和UploadFactory 对象,也会带来维护问题。
享元模式适用场景:

上一篇 下一篇

猜你喜欢

热点阅读