虚拟代理
使用虚拟代理实现延迟加载和性能优化
问题
什么是虚拟代理?如何在前端中应用虚拟代理模式?
解答
虚拟代理是代理模式的一种,用于延迟创建开销大的对象,直到真正需要时才创建。代理对象控制对真实对象的访问,在访问前可以执行额外操作。
图片懒加载
最常见的虚拟代理应用:
// 真实图片加载器
class RealImage {
constructor(container, src) {
this.img = document.createElement('img')
this.img.src = src
container.appendChild(this.img)
}
}
// 虚拟代理:先显示占位图,加载完成后替换
class ProxyImage {
constructor(container, src) {
this.container = container
this.src = src
this.realImage = null
// 先显示占位图
this.placeholder = document.createElement('img')
this.placeholder.src = 'loading.gif'
container.appendChild(this.placeholder)
// 后台加载真实图片
this.loadImage()
}
loadImage() {
const img = new Image()
img.onload = () => {
// 加载完成,替换占位图
this.placeholder.src = this.src
}
img.src = this.src
}
}
// 使用
const container = document.getElementById('image-container')
new ProxyImage(container, 'large-image.jpg')
延迟初始化
对于创建成本高的对象,延迟到首次使用时才初始化:
// 假设这是一个初始化很慢的编辑器
class HeavyEditor {
constructor() {
console.log('初始化编辑器...耗时操作')
this.content = ''
}
setContent(text) {
this.content = text
}
getContent() {
return this.content
}
}
// 虚拟代理:延迟创建
class EditorProxy {
constructor() {
this.editor = null // 延迟创建
}
// 获取真实对象,不存在则创建
getEditor() {
if (!this.editor) {
this.editor = new HeavyEditor()
}
return this.editor
}
setContent(text) {
this.getEditor().setContent(text)
}
getContent() {
return this.getEditor().getContent()
}
}
// 使用
const editor = new EditorProxy() // 此时不会初始化 HeavyEditor
console.log('代理已创建')
// 首次调用时才初始化
editor.setContent('Hello') // 这里才会初始化 HeavyEditor
使用 Proxy 实现
ES6 Proxy 可以更优雅地实现虚拟代理:
function createLazyProxy(factory) {
let instance = null
return new Proxy({}, {
get(target, prop) {
// 首次访问时创建实例
if (!instance) {
instance = factory()
}
const value = instance[prop]
// 如果是方法,绑定 this
return typeof value === 'function'
? value.bind(instance)
: value
}
})
}
// 使用
const lazyEditor = createLazyProxy(() => {
console.log('创建编辑器实例')
return new HeavyEditor()
})
console.log('代理已创建,实例未创建')
lazyEditor.setContent('test') // 此时才创建实例
合并请求
虚拟代理还可用于合并短时间内的多次请求:
// 合并文件同步请求
class SyncProxy {
constructor(syncFn) {
this.syncFn = syncFn
this.cache = new Set()
this.timer = null
}
sync(fileId) {
this.cache.add(fileId)
// 清除之前的定时器
if (this.timer) {
clearTimeout(this.timer)
}
// 延迟执行,合并请求
this.timer = setTimeout(() => {
this.syncFn([...this.cache])
this.cache.clear()
}, 2000)
}
}
// 使用
const syncProxy = new SyncProxy((ids) => {
console.log('批量同步文件:', ids)
})
// 多次调用会被合并
syncProxy.sync('file1')
syncProxy.sync('file2')
syncProxy.sync('file3')
// 2秒后只发送一次请求:['file1', 'file2', 'file3']
关键点
- 虚拟代理延迟创建开销大的对象,直到真正需要时才实例化
- 图片懒加载是最典型的应用场景
- ES6 Proxy 可以透明地拦截对象访问,实现更优雅的虚拟代理
- 虚拟代理还可用于合并请求、缓存结果等场景
- 代理对象与真实对象接口一致,对调用者透明
目录