《js 设计模式与开发实践》读书笔记 12

 享元 flyweight 模式是一种性能优化模式,享元可以理解成共享元对象。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式的概念听起来不太好理解,我们来看一个例子。

  假设爱慕的产品。有 59 种男士内衣,50 件女士内衣。为了推销产品,工厂决定生产一些塑料模特来穿上他们的内衣拍成广告照片。正常情况下需要 50 个男模特和 50 个女模特。假如不用享元模式,可能会写成这样。后面的代码会越来越不像人写的。我们只看思想。然后看如何结合我们的写法。

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

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

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

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

// 修改
var Model = function (sex, underwear) {
  this.sex = sex
}

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

var maleModel = new Model('male')
var femaleMode = new Model('female')

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

for (var j = 1; j <= 50; j++) {
  femaleModel.underwear = 'underwear ' + j
  femaleModel.takePhoto()
}

  改进之后,就是只需要两个对象就可以了。不需要重复创建很多对象。上面的代码是享元模式的雏形,它要求将对象的属性划分为内部属性和外部属性。享元模式的目标是尽量减少共享对象的数量。关于如何划分内部状态和外部状态。可以通过几点来做区分。1 内部状态存储于对象内部。2 内部状态可以被一些对象共享。3 内部状态独立于具体的场景,通常不会改变。4 外部状态取决于具体的场景,并根据场景而变化。外部状态不能背共享。(完)通过拆分内部和外部属性,虽然组合外部属性时会花费一些时间,但可以减少系统中的对象数量。所以这是一种时间换空间的优化模式。

  上面代码中有两个问题。1 我们通过 new 了两个 model 对象,在其他系统中,也许并不是一开始就需要所有的共享对象。2 给 model 对象设置了 underware 的外部属性,在复杂系统中,这不是一个好的方式。我们通过对象工厂来解决第一个问题,第二个问题,可以用一个管理器来记录对象相关的外部状态。使这些外部状态通过某个钩子和共享对象联系起来。

  我们讲下对象池,对象池维护一个装载空闲对象的池子,如果需要对象的时候,不是之间 new,而是从对象池中获取。如果对象池里么有空闲对象,则创建一个新的对象,当获取出的对象完成它的职责之后,再进入池子等待下次获取。对象池技术的应用非常广泛,http 连接池和数据库连接池都是其代表应用。在 web 前端开发中,对象池使用最多的场景大概就是跟 dom 有关的操作。假设我们开发一个地图应用,地图上经常会出现一些标志地名的小气泡,我们叫他 toolTip。比如搜索我们附近时出现两个小气泡,搜索兰州拉面的时候,出现了 6 个。按照对象池的思想,在第二次搜索开始之前,不会把第一次创建的 2 个小气泡删除掉,而是把他们放进对象池,这样第第二次的搜索结果页面里,我们只需要再创建 4 个小气泡而不是 6 个。我们先定义一个获取小气泡节点的工厂,作为对象池的数组成为私有属性被包含在工厂闭包里,这个工厂有两个暴露对外的方法,create 表示获取一个 div 节点,recover 表示回收一个 div 节点。

var toolTipFactory = (function () {
  var toolTipPool = []

  return {
    create: function () {
      if (toolTipPool.length === 0) {
        var div = document.createElement('div')
        document.body.appendChild(div)
        return div
      } else {
        return toolTipPool.shift()
      }
    },
    recover: function (tooltipDom) {
      return toolTipPool.push(tooltipDom)
    }
  }
})()

var ary = []
var str = ['a', 'b']
for (var i = 0; i < str.length; i++) {
  var toolTip = toolTipFactory.create()
  toolTip.innerHTML = str[i]
  ary.push(toolTip)
}

  接下来假设地图需要开始重新绘制,在此之前需要把两个节点回收到对象池,然后再生成 6 个气泡。

document.getElementById('button1').onclick = function () {
  for (let i = 0; i < ary.length; i++) {
    toolTipFactory.recover(toolTip)
  }
  let str = ['a', 'b', 'c', 'd', 'e', 'f']
  for (let i = 0; i < str.length; i++) {
    const el = str[i]
    let tt = toolTipFactory.create()
    tt.innerHTML = el
  }
}

  现在再测试一番,页面中出现了6个节点。对象池和享元模式的思想有点相似,虽然这里没有粉笔内部属性和外部属性。以后遇到大量相似对象的系统,享元模式的思想可以给我们解决性能问题。

posted @ 2022-09-16 17:58  艾路  阅读(13)  评论(0编辑  收藏  举报