Fork me on GitHub

前端设计模式之【代理模式】

前端设计模式之代理模式!

一、最简单的代理 —— Proxy

二、事件代理

加入父元素下有多个子元素,该所有的子元素都要进行事件监听,如果给每一个子元素绑定监听事件,就会导致创建多个私有作用域,在性能上会有很大的开销。

1
2
3
4
5
6
7
8
9
10
11
/ 假如不用代理模式,我们将循环安装监听函数
const aNodes = document.getElementById('father').getElementsByTagName('a')

const aLength = aNodes.length

for(let i=0;i<aLength;i++) {
aNodes[i].addEventListener('click', function(e) {
e.preventDefault()
alert(`我是${aNodes[i].innerText}`)
})
}

如果使用代理模式,在其父容器上设置监听,则可以减少性能的开销,而且代码也简单了很多。

1
2
3
4
5
6
7
8
9
10
11
12
// 获取父元素
const father = document.getElementById('father')

// 给父元素安装一次监听函数
father.addEventListener('click', function(e) {
// 识别是否是目标子元素
if(e.target.tagName === 'A') {
// 以下是监听函数的函数体
e.preventDefault()
alert(`我是${e.target.innerText}`)
}
} )

通过父元素代理、分发的方式,间接的将其作用于子元素,这种模式叫做事件代理

三、虚拟代理

在性能优化里边,图片的优化有两种加载方式,分别为懒加载预加载

  • 懒加载:针对于图片加载时机的优化。在一些图片量比较大的网站,用户打开网页尝试把所有的页面加载完,会导致白屏和卡顿情况。所以采取“先占位,后加载”的策略。

  • 预加载:预加载主要为了避免网络不好、图片太大时,页面长时间给用户留空白。所以需要一个 img 的标签占位符,然后创建一个 images 实例,让 src 指向图片的真实地址。 —— 其对应的真实图片加载完毕后,即已经有了该图片的缓存内容,再将 DOM 上的 img 元素的 src 指向真实的目标图片地址。

3.1 预加载

一下代码存在问题,就是 PreLoadImage 要做的事情太多,不仅要负责图片的加载,还要负责 DOM 层面的操作,严重违反了设计模式中的单一职责原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class PreLoadImage {
// 占位图的url地址
static LOADING_URL = 'xxxxxx'

constructor(imgNode) {
// 获取该实例对应的DOM节点
this.imgNode = imgNode
}

// 该方法用于设置真实的图片地址
setSrc(targetUrl) {

// img节点初始化时展示的是一个占位图
this.imgNode.src = PreLoadImage.LOADING_URL

// 创建一个帮我们加载图片的Image实例
const image = new Image()

// 设置src属性,Image实例开始加载图片
image.src = srcUrl

// 监听目标图片加载的情况,完成时再将DOM上的img节点的src属性设置为目标图片的url
image.onload = () => {
this.imgNode.src = targetUrl
}
}
}

我们试着将两者进行分离,分离之后,需要借助一个代理器将它们进行连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class PreLoadImage {
constructor(imgNode) {
// 获取真实的DOM节点
this.imgNode = imgNode
}

// 操作img节点的src属性
setSrc(imgUrl) {
this.imgNode.src = imgUrl
}
}

class ProxyImage {
// 占位图的url地址
static LOADING_URL = 'xxxxxx'

constructor(targetImage) {
// 目标Image,即PreLoadImage实例
this.targetImage = targetImage
}

// 该方法主要操作虚拟Image,完成加载
setSrc(targetUrl) {
// 真实img节点初始化时展示的是一个占位图
this.targetImage.setSrc(ProxyImage.LOADING_URL)

// 创建一个帮我们加载图片的虚拟Image实例
const virtualImage = new Image()

// 设置src属性,虚拟Image实例开始加载图片
virtualImage.src = targetUrl

// 监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url
virtualImage.onload = () => {
this.targetImage.setSrc(targetUrl)
}
}
}

在这个实例中,virtualImage 这个对象它始终存在于 JavaScript 世界中、代替真实 DOM 发起了图片加载请求、完成了图片加载工作,却从未在渲染层面抛头露面。因此这种模式被称为“虚拟代理”模式。

四、缓存代理

在一些大的计算场景,用到“空间换时间”的思想,当我们需要用到某个计算值的时候,每次都不用去计算,而是计算一次就放入到缓存中,下次直接在缓存中取出使用即可,大大增加了系统的性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// addAll方法会对你传入的所有参数做求和操作
const addAll = function() {
console.log('进行了一次新计算')
let result = 0
const len = arguments.length
for(let i = 0; i < len; i++) {
result += arguments[i]
}
return result
}

// 为求和方法创建代理
const proxyAddAll = (function(){
// 求和结果的缓存池
const resultCache = {}
return function() {
// 将入参转化为一个唯一的入参字符串
const args = Array.prototype.join.call(arguments, ',')

// 检查本次入参是否有对应的计算结果
if(args in resultCache) {
// 如果有,则返回缓存池里现成的结果
return resultCache[args]
}
return resultCache[args] = addAll(...arguments)
}
})()

五、保护代理

在 ES6 中的 Proxy。