持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情
享元模式(Flyweight): 运用共享技术有效地支持大量地细粒度地对象,避免对象之间拥有相同内容造成多余的开销。
这个模式的目的是 为了提高程序的执行效率以及系统的性能。 它可以避免程序中的数据重复,避免系统内存中存在着大量重复的对象而造成的大量内存占用。即 享元模式可以用来减少内存的消耗。
凡事有利有弊,享元模式在减少内存消耗的同时,需要将一些 不能共享的状态外部化, 进而导致程序的复杂性增加,因此在一些小项目中,内存的消耗与性能对程序执行影响不大的项目中,不建议强行引入享元模式。
假设我们现在有1000条数据,要前端进行分页显示,每页显示5条,于是有以下的解决思路。
上面的代码思路就是,按照数据data的长度创建等数量的dom元素,然后通过page当前页数变量和size每页条目数变量来控制这些dom元素的显隐,从而达到了分页的效果。
这种实现方式对内存的消耗大小跟数据量的多少挂钩,假如有一万条数据,我们就要创建一万个元素,有十万条数据就要创建十万个元素,进而会导致对内存的消耗变得越来越大,从而会导致浏览器的页面卡顿。
我们换个角度来想想,真的有必要创建和数据量等量的元素吗?页面中每条数据的结构是相同的,我们为什么不创建和每页条目数size挂钩的元素,然后每次翻页的时候只是更改元素的内容呢?这样一来,假设每页显示5条数据,那么即使有1000条数据,我们也只需要创建5个元素,让这1000条数据共享这5个元素,从而导致内存开销大大的减少。这就是享元模式的思想。
享元模式主要是对其数据、方法共享分离,它将数据和方法分为 内部数据、内部方法和外部数据、外部方法。 而内部数据和内部方法则指的就是 相似或者共有的数据和方法, 也是需要我们提取出来共享的部分。
享元对象
在上面的需求中,data中的数据是内部数据,而翻页按钮绑定的事件已经不能再继续抽象提取了,所以它是外部方法。我们将内部数据提取出来之后,还需要提供一个操作方法来使用它们。
const Flyweight = function () {
const created = [] // 用来缓存已经创建的元素
// 创建一个数据包装容器
function create() {
const li = document.createElement('li') // 创建新元素
// 将元素插入列表容器中
document.getElementById('ulContainer').appendChild(li)
created.push(li) // 缓存新创建的元素
return li // 返回创建的新元素
}
return {
getLi: function () {
// 假如已经创建的元素小于每页条目数,则继续常见
if (created.length < 5) {
return create()
} else {
// 否则的话,复用第一个元素,并插入末尾
const li = created.shift()
created.push(li)
return li
}
}
}
}
上面的享元类创建好之后,我们使用享元类进行需求实现。
let page = 0,
size = 5,
len = data.length
const flyweight = Flyweight()
// 先显示5条数据
for (let i = 0; i < 5; i++) {
if (data[i]) {
flyweight.getLi().innerHTML = data[i]
}
}
// 翻页按钮事件
document.getElementById('next').onclick = function () {
// 如果数组长度不足5条则不执行翻页操作
if (data.length < 5) return
const n = ++page * size % len // 获取当前页第一条新闻的索引
// 插入5条新闻
for (let j = 0; j < 5; j++) {
// 按顺序插入
if (data[n + j]) {
flyweight.getLi().innerHTML = data[n + j]
}
// 从起始位置的第n+j+len开始插入
else if (data[n + j - len]) {
flyweight.getLi().innerHTML = data[n + j - len]
}
// 上述两种情况都不存在则插入空字符串
else {
flyweight.getLi().innerHTML = ''
}
}
}
小结
享元模式的本质是缓存共享对象,降低内存消耗。