【JS设计模式】城市公交车——享元模式

128 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情

享元模式(Flyweight): 运用共享技术有效地支持大量地细粒度地对象,避免对象之间拥有相同内容造成多余的开销。

这个模式的目的是 为了提高程序的执行效率以及系统的性能。 它可以避免程序中的数据重复,避免系统内存中存在着大量重复的对象而造成的大量内存占用。即 享元模式可以用来减少内存的消耗。

凡事有利有弊,享元模式在减少内存消耗的同时,需要将一些 不能共享的状态外部化, 进而导致程序的复杂性增加,因此在一些小项目中,内存的消耗与性能对程序执行影响不大的项目中,不建议强行引入享元模式。

假设我们现在有1000条数据,要前端进行分页显示,每页显示5条,于是有以下的解决思路。

image.png

上面的代码思路就是,按照数据data的长度创建等数量的dom元素,然后通过page当前页数变量和size每页条目数变量来控制这些dom元素的显隐,从而达到了分页的效果。

这种实现方式对内存的消耗大小跟数据量的多少挂钩,假如有一万条数据,我们就要创建一万个元素,有十万条数据就要创建十万个元素,进而会导致对内存的消耗变得越来越大,从而会导致浏览器的页面卡顿。

20220605_150910.gif

我们换个角度来想想,真的有必要创建和数据量等量的元素吗?页面中每条数据的结构是相同的,我们为什么不创建和每页条目数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 = ''
        }
    }
}

20220605_151207.gif

小结

享元模式的本质是缓存共享对象,降低内存消耗。