美文网首页前端知识总结js设计模式
js常用设计模式7-享元模式

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

作者: 青色琉璃 | 来源:发表于2021-01-18 11:44 被阅读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 对象,也会带来维护问题。
    享元模式适用场景:

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

    相关文章

      网友评论

        本文标题:js常用设计模式7-享元模式

        本文链接:https://www.haomeiwen.com/subject/epaoaktx.html