阅读 794

设计模式-原来这就是代理模式(三)

这是我参与更文挑战的第1天,活动详情查看: 更文挑战

一、什么是代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。

代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。

二、模拟场景

1. 小明送花给小白

1.1 传统做法

传统做法是小明直接把花送给小白,小白接收到花,代码如下:

const Flower = function () {
	return '玫瑰🌹'
}

const xiaoming = {
	sendFlower: target => {
		const flower = new Flower()
		target.receiveFlower(flower)
	}
}

const xiaobai = {
	receiveFlower: flower => {
		console.log('收到花', flower)
	}
}

xiaoming.sendFlower(xiaobai)
复制代码

1.2 代理模式

但是,小明并不认识小白,他想要通过小代,帮他打探小白的情况,在小白心情好的时候送花,这样成功率更高。代码如下:

const Flower = function () {
	return '玫瑰🌹'
}

const xiaoming = {
	sendFlower: target => {
		const flower = new Flower()
		target.receiveFlower(flower)
	}
}

const xiaodai = {
	receiveFlower: flower => {
		xiaobai.listenGoodMood().then(() => {
			xiaobai.receiveFlower(flower)
		})
	}
}

const xiaobai = {
	receiveFlower: flower => {
		console.log('收到花', flower)
	},
	listenGoodMood: fn => {
		return new Promise((reslove, reject) => {
			// 10秒后,心情变好
			reslove()
		})
	}
}

xiaoming.sendFlower(xiaodai)
复制代码

以上,小明通过小代,监听到小白心情的心情变化,选择在小白心情好时送花给小白。不仅如此,小代还可以做以下事情:

  1. 帮助小白过滤掉一些送花的请求,这就叫做保护代理;
  2. 帮助小明,在小白心情好时,再执行买花操作,这就叫做虚拟代理。虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。

三、实际场景

1. 图片预加载

图片预加载时一种常见的技术,如果直接给img标签节点设置src属性,由于图片过大或网络不佳,图片的位置往往有一段时间时空白。

1.1 传统做法

const myImage = (() => {
	const imgNode = document.createElement('img')
	document.body.appendChild(imgNode)

	return {
		setSrc: src => {
			imgNode.src = src
		}
	}
})()

myImage.setSrc('https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa98e67c4708449eb6894c7133d93774~tplv-k3u1fbpfcp-watermark.image')

复制代码

通过开发者工具把网速设置为 5kb/s时,会发现在很长一段时间内,图片位置是空白的。

image.png

1.2 虚拟代理

下面用虚拟代理优化该功能,把加载图片的操作交给代理函数完成,在图片加载时,先用一张loading图占位,当图片加载成功后,再把它填充进img节点。

代码如下:

const myImage = (() => {
	const imgNode = document.createElement('img')
	document.body.appendChild(imgNode)

	return {
		setSrc: src => {
			imgNode.src = src
		}
	}
})()

const loadingSrc = '../../../../img/loading.gif'
const imgSrc = 'https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa98e67c4708449eb6894c7133d93774~tplv-k3u1fbpfcp-watermark.image'

const proxyImage = (function () {
	const img = new Image()
	img.onload = () => {
		myImage.setSrc(img.src)
	}

	return {
		setSrc: src => {
			myImage.setSrc(loadingSrc)
			img.src = src
		}
	}
})()

proxyImage.setSrc(imgSrc)
复制代码

上述代码有以下优点:

  1. 通过 proxyImage 控制了对 MyImage 的访问,在 MyImage 未加载成功之前,使用 loading 图占位;

  2. 践行单一职责原则,给 img 节点设置 src 的函数 MyImage,预加载图片的函数 proxyImage,都只有一个职责;

  3. 践行开放-封闭原则,给 img 节点设置 src 和预加载图片的功能,被隔离在两个对象里,它们可以各自变化不影响对方。

2.合并HTTP请求

假设我们要实现一个同步文件的功能,通过复选框,当复选框选中的时候,将该复选框对应的id传给服务器,告诉服务器需要同步 id 对应的文件。

思考一下,会发现,如果每选中一个复选框,就请求一次接口,假设 1s 内选中了 10 个复选框,那么就要发送 10 次请求。

2.1 虚拟代理

可以通过虚拟代理来优化上述做法,新增一个代理,帮助复选框发起同步文件的请求,收集在这 1s 内的请求,1s 后再一起把这些文件 id 发送到服务器。

代码如下:

<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
	<title></title>
</head>
<body>
  a <input type="checkbox" value="a" />
  b <input type="checkbox" value="b" />
  c <input type="checkbox" value="c" />
  d <input type="checkbox" value="d" />
	<script type="text/javascript" src="index.js">
	</script>
</body> 
</html>
复制代码
const synchronousFile = cache => {
  console.log('开始同步文件,id为:'+ cache.join('/'))
}

const proxySynchronousFile = (() => {
  const cache = []

  let timer

  return id => {
    console.log(id)
    cache.push(id)

    if (timer) {
      return
    }

    timer = setTimeout(() => {
      synchronousFile(cache)
      clearTimeout(timer)
      timer = null
      cache.length = 0
    }, 2000)
  }
})()

const checkbox = document.getElementsByTagName('input')

Array.from(checkbox).forEach(i => {
  console.log(i)
  i.onclick = () => {
    if (i.checked) {
      proxySynchronousFile(i.value)
    }
  }
})


复制代码

github 源码地址

3. ajax异步请求数据

在列表需要分页时,同一页的数据理论上只需要去后台拉取一次,可以把这些拉取过的数据缓存下来,下次请求时直接使用缓存数据

3.1 缓存代理

使用缓存代理实现上述功能,代码如下:

(async function () {
  function getArticle (currentPage, pageSize) {
    console.log('getArticle', currentPage, pageSize)
    // 模拟一个ajax请求
    return new Promise((resolve, reject) => {
      resolve({
        ok: true,
        data: {
          list: [],
          total: 10,
          params: {
            currentPage, 
            pageSize
          }
        }
      })
    })
  }
  
  const proxyGetArticle = (() => {
    const caches = []
  
    return async (currentPage, pageSize) => {
  
      const cache = Array.prototype.join.call([currentPage, pageSize],',')
  
      if (cache in caches) {
        return caches[cache]
      }
      const { data, ok } = await getArticle(currentPage, pageSize)
  
      if (ok) {
        caches[cache] = data
      }
  
      return caches[cache]
    }
  })()

  // 搜索第一页
  await proxyGetArticle(1, 10)
  
  // 搜索第二页
  await proxyGetArticle(2, 10)

  // 再次搜索第一页
  await proxyGetArticle(1, 10)
  
})()


复制代码

通过缓存代理,在第二次请求第一页的数据时,直接在缓存数据中拉取,无须再次从服务器请求数据。

四、小结

上面根据实际场景介绍了虚拟代理和缓存代理的做法。

当我们不方便直接访问某个对象时,找一个代理方法帮我们去访问该对象,这就是代理模式。

可通过github源码进行实操练习。

希望能对你有所帮助,感谢阅读~别忘了点个赞鼓励一下我哦,笔芯❤️


· 往期精彩 ·

【设计模式-谁没遇见过几个单例模式(一)】

【设计模式-什么是快乐星球,什么是策略模式(二)】

【设计模式-原来这就是代理模式(三)】

【设计模式-简单易懂的观察者模式(四)】

【设计模式-不会吧,不会还有人不知道装饰器模式吧(五)】

文章分类
前端