之前面试的时候被问到,如何在前端实现分页。听到这个问题,我当时有点懵,因为我不太确定面试官想问的是什么。当时的想法是,如果要在前端进行分页,那只能一次性拿到所有的数据,然后根据一个表示当前页数的变量来选择性地显示某些数据记录。这样想着就实在想不到面试官想问的点是什么。说实话,直到现在,我还不是很清楚这个问题考察的是什么,不过最近刚好在看《JavaScript 设计模式》,书中在享元模式一章提到了前端分页的概念。觉得挺有意思,就拿过来操练一下。另外,要理解设计模式的内容都是要抛开现成的前端框架来看的哈。
1. 享元模式
先来看一下概念:享元模式是运用共享技术有效地支持大量的细粒度的对象,避免对象间拥有相同内容造成多余的开销。啥意思,看了三遍还是没明白。再往下翻翻,终于看到一句看起来稍微清晰一点的话:享元模式主要是对数据、方法共享分离,它将数据和方法分为内部数据、内部方法,外部数据、外部方法。看到这里,对享元模式的大概印象就是数据与方法解耦,不过感觉设计模式中的好多方法都是用来解耦的哎。好,来看案例。
2. 前端分页
分页的特点是每一页的内容结构基本相同,只是数据不同而已。结合上面提到的内部外部来看的话,每一页的数据是其内部数据,获取和修改每一页内容的方法是其内部方法。而当前的页数和上一页、下一页的按钮则是外部的数据与方法。在这个场景中我们要做的是在外部方法中调用内部方法修改内部数据。
也就是说内部需要向外部提供一个可以获操作内部数据的操作方法,这个操作方法我们可以将其看成享元模式的载体。因为要实现数据与方法的解耦,而数据只能放在外层,所以操作dom 的方法要放在内层,通过在外层的按钮事件监听(外部方法)中使用内部方法获取到dom 并赋值的方式来实现翻页操作,内部操作方法代码如下:
var sharedMode = function() {
// 已创建的元素
var created = [];
function create() {
// 元素数量没达到num 之前创建并插入一个新元素
var dom = document.createElement('div');
document.getElementById('container').appendChild(dom);
created.push(dom);
return dom;
}
return {
// 获取当前要修改数据的dom
getDiv: function () {
if(created.length < num) {
return create();
} else {
// 拿到第一个元素
var div = created.shift();
// 放到最后
created.push(div);
return div;
}
}
}
}();
内部方法写好了,那我们如何在外层按钮点击事件中更改dom 显示的内容呢,这个就很简单了,使用变量标一下当前的页数,然后根据页数去数据列表中取值就可以了,代码如下:
// 每页的记录条数
var num = 4;
// 所有数据
var article = [
'di 1 tiao',
'di 2 tiao',
'di 3 tiao',
'di 4 tiao',
'di 5 tiao',
'di 6 tiao',
'di 7 tiao',
'di 8 tiao',
];
// 当前页数
var page = 0,
len = article.length,
// 当前页的第一条记录的下标
nowIndex = 0;
// 初始化第一页内容
for(var i = 0; i < num; i++) {
sharedMode.getDiv().innerHTML = article[i];
}
// 点击下一页
document.getElementsByClassName('next')[0].onclick = function () {
// 不足num条记录或当前页是最后一页
if(article.length < num || len <= nowIndex + num) {
alert('没有下一页了!');
return;
}
nowIndex = ++page * num;
for(var j = 0;j < num; j++) {
if(article[nowIndex + j]) {
sharedMode.getDiv().innerHTML = article[nowIndex + j];
} else {
sharedMode.getDiv().innerHTML = "";
}
}
};
// 点击上一页
document.getElementsByClassName('prev')[0].onclick = function () {
// 当前是第一页
if(nowIndex <= 0) {
alert('没有上一页了!');
return;
}
nowIndex = --page * num;
for(var j = 0;j < num; j++) {
if(article[nowIndex + j]) {
sharedMode.getDiv().innerHTML = article[nowIndex + j];
} else {
sharedMode.getDiv().innerHTML = "";
}
}
};
最后,贴一下html,方便直接拿下来运行:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>享元模式</title>
</head>
<body>
<div id="container"></div>
<button class="prev">prev</button>
<button class="next">next</button>
</body>
</html>
3. 意义是什么
写完以后回头来看一下,利用享原元模式实现前端分页的意义是什么呢?在sharedMode 中的create 方法中我们可以看到,每次只是创建并插入了一个简单的div。想象一下,如果我们的页面展示比较复杂,每条数据记录要展示的东西很多,不光包含文字,还要有图片和其他东西,这样dom 操作就不是三两行代码能写清楚的了。
这个时候如果没有享元模式怎么办?如果让我来写,只能把dom 相关操作写在按钮的事件监听方法中,这样的话,有几个按钮就要写几次,而且修改的时候很麻烦。所以答案显而易见,享元模式让我们的代码可维护性和可扩展性更好了,这也是很多设计模式在做的事情。光是这一点我觉得就已经足够吸引人了,你们觉得呢?