Appearance
虚拟代理实现图片预加载
如果直接给某个 img 标签节点设置 src 属性,由于图片过大或者网络不佳,图片的位置往往有段 时间会是一片空白。常见的做法是先用一张 loading 图片占位,然后用异步的方式加载图片,等 图片加载好了再把它填充到 img 节点里,这种场景就很适合使用虚拟代理。
首先创建一个普通的本体对象,这个对象负责往页面中创建一个 img 标签,并且提供一个对外的 setSrc 接口,外界调用这个接口,便可以给该 img 标签设置 src 属性。
javascript
const myImage = (function () {
const imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc (src) {
imgNode.src = src
}
}
})()
myImage.setSrc('https://avatars.githubusercontent.com/u/84729241?v=4')把网速调至 100KB/s,然后通过 MyImage.setSrc 给该 img 节点设置 src,可以看到,在图片 被加载好之前,页面中有一段长长的空白时间。
现在开始引入代理对象 proxyImage,通过这个代理对象,在图片被真正加载好之前,页面中 将出现一张占位的 loading.gif, 来提示用户图片正在加载。
javascript
const proxyImage = (function () {
const img = new Image()
img.addEventListener('load', (e) => {
myImage.setSrc(e.target.src) // 替换 src
})
return {
setSrc (src) {
// 先加载 loading.gif,等到真正的图片加载完成后把 src 替换成真正的图片。
myImage.setSrc('./loading.gif')
img.src = src
}
}
})()
proxyImage.setSrc('https://avatars.githubusercontent.com/u/84729241?v=4')一个小小的图片预加载功能,即使不需要引入任何模式也能办到。
javascript
const myImage = (function () {
const imgNode = document.createElement('img')
document.body.appendChild(imgNode)
const img = new Image()
img.addEventListener('load', function () {
imgNode.src = this.src
})
return {
setSrc (src) {
imgNode.src = './loading.gif'
img.src = src
}
}
})()
myImage.setSrc('https://avatars.githubusercontent.com/u/84729241?v=4')为了说明代理的意义,下面我们引入一个面向对象设计的原则——单一职责原则。单一职责原则指的 是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象 承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计 鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一 起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏。 职责被定义为“引起变化的原因”。上段代码中的 MyImage 对象除了负责给 img 节点设置 src 外,还要负责预加载图片。我们在处理其中一个职责时,有可能因为其强耦合性影响另外一个职 责的实现。另外,在面向对象的程序设计中,大多数情况下,若违反其他任何原则,同时将违反 开放—封闭原则。如果我们只是从网络上获取一些体积很小的图片,或者 5 年后的网速快到根本不 再需要预加载,我们可能希望把预加载图片的这段代码从 MyImage 对象里删掉。这时候就不得不 改动 MyImage 对象了。实际上,我们需要的只是给 img 节点设置 src,预加载图片只是一个锦 上添花的功能。如果能把这个操作放在另一个对象里面,自然是一个非常好的方法。于是代理的作 用在这里就体现出来了,代理负责预加载图片,预加载的操作完成之后,把请求重新交给本体 MyImage。纵观整个程序,我们并没有改变或者增加 MyImage 的接口,但是通过代理对象,实际 上给系统添加了新的行为。这是符合开放—封闭原则的。给 img 节点设置 src 和图片预加载这两 个功能,被隔离在两个对象里,它们可以各自变化而不影响对方。何况就算有一天我们不再需要预 加载,那么只需要改成请求本体而不是请求代理对象即可。