做分页器时的一点感触

1. 问题的由来

前段时间要做一个分页器, 大概是下面这个样子:

1 2 3 ... 7 [8] 9 ... 12 13 14

只要有一点相关经验的人都知道, 这个看似简单的东西, 实现起来其实是很麻烦的. 原因在于, 你面对的"总页数和当前页的关系", 不一定是上面这种"理想情况". 比如:

一共只有 3 页:

1 2 3

一共有 4 页:

1 2 3 4

一共有 6 页:

1 2 3 4 5 6

一共有 7 页(当前页是第 6 页):

1 2 3 ... 5 [6] 7

一共有 7 页(当前页是第 4 页):

1 2 3 [4] 5 6 7

一共有 14 页, 当前页是 13 页(最开始的例子, 当前页是第 8 页):

1 2 3 ... 12 [13] 14

上面几个简单的例子, 足以说明问题, 这玩意儿真的很麻烦, 如果你用传统的方法去做.

回到理想情况的例子:

1 2 3 ... 7 [8] 9 ... 12 13 14

这个分页器的显示规则, 大概可以描述成 " 显示前 3 页, 显示后 3 页, 以及当前页的前后 1 页 " .

或者, 可以扩展为 " 显示前 L 页, 显示后 R 页, 以及当前页的前后 M 页 " .

2. 自然的, 麻烦的思路

1 2 3 ... 7 [8] 9 ... 12 13 14

要实现这个东西, 可能大部分人(包括我)一来的想法, 就是通过总页数与当前页的条件, 去计算 左 / 中 / 右 三部分的结果. 整个计算过程穷举所有可能的情况, 即处理所有的状态.

这样做不能说不行, 因为市面上的好多分页器大概也是这样干的吧. 理论上可行, 但是实践中, 这样做的话成本太大了. 通过前面的例子也看到的, 面对的各种状态情况, 可以说非常非常复杂. 即使做出来了, 后续的维护, 需求变更的响应, 这些仍然需要继续以很大的成本去投入它, 稍有不慎, 出了问题要想定位到问题都困难吧, 在一堆涉及各种状态的代码中.

3. 换个角度入手

1 2 3 ... 7 [8] 9 ... 12 13 14

  L   PRE    M    POST   R

理想情况下的结果, 其实也是一个"完备"的结果. 我们从这个形式着手, 细致地分析一下我们面对的到底是什么东西.

可以看到, 完整的结果, 一共是由 5 部分构成的, L , PRE , M , POST , R .

但是在一些"特殊"情况下, 这 5 部分不是全都出现的, 比如, 一共有 7 页(当前页是第 6 页):

1 2 3 ... 5 [6] 7

这时, 结果中只有 L , PRE, R(M) 这几部分(最后即可以看成是 M , 也可以看成是 R ).

传统的思路, 我们会去想, 通过计算, 算出 L , PRE , M , POST , R 这 5 部分的结果(包括它们是否应该存在), 但是, 这样很难.

要得到结果, 我们还有另外一种选择, 那就是首先, 我们忽略状态, 根据具体的规则, 先计算出结果.

规则是我们之前提到的: " 显示前 3 页, 显示后 3 页, 以及当前页的前后 1 页 " . 假设现在共有 S 页, 当前页是第 P 页, 那么结果就是:

L = [1, 2, 3]
PRE = True
M = [P - 1, P, P + 1]
POST = True
R = [S - 2 , S - 1 , S]

那么我们就可以得到:

1 2 3 ... P-1 [P] P+1 ... S-2 S-1 S

毫无疑问, 上面的计算与结果都没有任何问题, 那我们拿一个"特殊"的实例来看看结果是什么, 就用前面的"一共有 S = 7 页, 当前是第 P = 6 页":

1 2 3 ... 5 [6] 7 ... 5 [6] 7

剩下的事情, 就是去修辞这个看起来不太对的结果了, 这部分其实很容易(但是容易遗漏).

  • 当 M[0] - L[-1] >= 2 时, PRE 才显示出来.
  • 当 R[0] - L[-1] >= 2 时, POST 才显示出来.
  • M 中只显示比 L[-1] 大的.
  • R 中只显示比 M[-1] 大的.
  • LM , R 中, 都只显示不比 S 大的.
  • LM , R 中, 都只显示比 0 大的(这点在计算 LMR 时就随便做了).

上面简单的几条规则, 就可以把不太对的结果, 修饰成(括号中的内容不会显示):

1 2 3 ... 5 [6] 7 (...) (5 [6] 7)

来看看更多的例子:

S=1, P=1:

[1] 2 3 ... 0 [1] 2 ... -1 0 1

-->

[1] (2 3) (...) (0 [1] 2) (...) (-1 0 1)

S=4, P=3:

1 2 [3] ... 2 [3] 4 .. 2 [3] 4

-->

1 2 [3] (...) (2 [3]) 4 (...) (2 [3] 4)

"修饰" 这部分, 实践中在一些框架的模板部分, 都可以直接做了.

4. 为何会有如此大的区别

换个角度去考虑之后, 一个原本麻烦的问题, 被轻松解决了. 之后我一直在想, 怎么会有如此大的区别呢?

我们实现的东西是一样的, 这就是说, 不管是传统的方法, 还是"变计算为应对"的方法, 它们的"描述能力"应该是一样的. 传统方法是在主动穷举所有的可能, 而后者则是在被动应对具体的可能.

当你在穷举所有可能的状态时, 不同类型的状态之间, 还有着相互的影响, 于是, 相互的作用大大放大了你面对的信息量. 这也许就是为什么我们会觉得非常麻烦的原因所在.

而当变成被动的应对这种方式时, 不同类型的状态之间, 相互的影响就不存在了(因为我们仅仅是信息的接收者, 没有创造信息, 也没有影响信息), 我们面对的只是独立的种种情况. 这种时候信息量就大大减少, 只需要简单地就每一种情况作出响应即可.

我知道上面的话很抽象, 其实我本身现在也无法对这种奇妙的区别作出一种具体的描述. 但要类比的话, 这个小小的分页器的实现, 以及大到系统架构的设计, 我觉得都跟绘画是一样的 -- 从大到小, 从抽象到具体, 从整体到局部 -- 如果一开始就从细节着力, 除非你已经胸有成竹, 否则连基本的正确比例都把握不了吧.

5. AngularJS的代码例子

最后, 附上使用 AngularJS 实现的上面说的分页器的简单代码:

scope.L_COUNT = 2;
scope.M_OFFSET = 1;
scope.R_COUNT = 2;

//计算左,中,右分页
var get_group = function(count, per_page, page){
    scope.sum_page = Math.ceil(count / per_page);
    if(page > scope.sum_page || page <= 0){page = 1; scope.page = page}
    scope.left = [];
    scope.middle = [];
    scope.right = [];
    for(var i = 0, l = scope.L_COUNT; i < l; i++){
        scope.left.push(i+1);
    }
    for(var i = 0, l = scope.M_OFFSET; i < l; i++){
        scope.middle.push(page-i-1);
    }
    scope.middle.push(page);
    for(var i = 0, l = scope.M_OFFSET; i < l; i++){
        scope.middle.push(page+i+1);
    }
    for(var i = 0, l = scope.R_COUNT; i < l; i++){
        scope.right.unshift(scope.sum_page-i);
    }
}

模板部分:

<div>
  <div ng-show="sum_page > 1">
    <div ng-repeat="o in left"
         ng-show="o <= sum_page" ng-click="goto(o)"
         ng-class="{true: 'u-current'}[o == page]">{{ o }}</div>

    <div ng-show="middle[0] > left[L_COUNT - 1] + 1">···</div>

    <div ng-repeat="o in middle"
         ng-show="o > left[L_COUNT - 1] && o <= sum_page"
         ng-click="goto(o)"
         ng-class="{true: 'u-current'}[o == page]">{{ o }}</div>

    <div ng-show="right[0] > middle[M_OFFSET * 2] + 1">···</div>

    <div ng-repeat="o in right"
         ng-show="o > middle[M_OFFSET * 2] && o <= sum_page"
         ng-click="goto(o)"
         ng-class="{true: 'u-current'}[o == page]">{{ o }}</div>
  </div>
</div>
时间: 2024-10-04 09:23:23

做分页器时的一点感触的相关文章

做SEO时Nofollow的一个误区和巧妙用处

在正式进入主题前,先简单介绍下这个nofollow怎么使用,相信还有部分做SEO的朋友对这个标签的使用不太了解的: a target="blank" href="ABC" rel="nofollow" >XXX 具体用处是什么呢?在写本文之前,我的理解跟很多人一样,加入nofollow标签是为了控制权重,即不让所在页面的权重传递给目标页面,这样一来,就相对地增加了传递给其他页面的权重. 这种观念从我在温州网络公司做SEO时,就一直深埋在我脑

浅谈在做seo时容易忽略的的几点错误(一)

很多朋友听到seo,或者一讲到seo,脑子里想的肯定就是选取目标关键词.每天坚持发外链,就达到了seo的目的了.其实,如果说seo只是完成选关键词以及发外链这两个工作就ok了,那就是真的打错特错了.因为seo,是一个系统的工程,除了发外链.选关键词.还有一系列的工作要做,因此本文就说说一些做seo时比较容易忽略的几点. 首先:把优化网站理解成优化首页 SEO优化的是整站而不仅仅是首页.很多朋友在给网站镶嵌关键词时,考虑的只是首页.网页标题以及描述也只是把首页弄好了就兴冲冲的区发外链了,全然不管内

做SEO时如何选个适合自己的老板

我们站长在做网站SEO的时候并不是一定给自己做的,很多时候也是给别人"打工",替别人的网站做SEO,这样,就有可能遇见各种各样的老板.有话说"千里马常有而伯乐不常有",站长们在给别人做站时要是遇见个内行的还好,要是遇见个外行的,只会无理由的给你提硬性要求时,简直让人有生不如死的感觉,因此,选好一个老板也是站长做SEO时很必要的生存手段!具体要如何去做,其实也简单,你要学会察言观色就可,避免开以下几种情况存在的老板就基本没多大问题了. 一.本身网站就很烂的老板 俗话说

从微软转投金山软件做CEO时激起的喧嚣

张宏江至今仍清楚地记得,两年前自己应雷军之邀,从微软转投金山软件做CEO时激起的喧嚣.一位资深媒体人甚至预言,他在金山不会超过一年. 但现在,张宏江已经品尝到初胜的喜悦.9月2日,雷军在微博发布配图消息,纪念"金山软件市值达200亿港币",张宏江兴致勃勃用红米手机转发,还附上一串大拇指:9月4日,金山软件官方微博发布消息,2013年初至发文时,金山软件股价的涨幅已达203%,同时配了一幅张宏江笑容可掬的照片. 自然,作为一家25岁的"老IT企业",金山的复兴之路走得

浅谈做SEO时应该要看的几个数据

做SEO时需要重视以下的几个数据: 1. 有多少人是经由搜索引擎来进入您的网站.这是最基本也是最重要的数据.若要更详细的话,我们最好能知道每个访客是由哪一个搜索引擎.透过哪一个关键词.以及从哪一个网页进入您的网站.当您第一次做这个分析时,一定会发现访客进入您网站的管道跟您原本的想法会有很大的出入. 如何找出这数据: 如果您能够有网站主机的浏览记录,那是最好的,因为这些信息都包括在浏览记录中.您可以自己写程序来分析,或是运用一些市面上的网络使用分析工具.若您无法得到这个信息(例如您想要看的是您的无

对硬盘做镜像时按位与按文件的区别

[网友问题] 对硬盘做镜像时,听说有按位与按文件两种,都有什么特点?都是用在什么情况下的?GHOST是哪种方式? [回答] 备份工作的按位(实际上是按扇区)转存意味着与文件系统无关.数据源是什么样子.有多大,目标就是什么样子,就有多大.即使没有分区.或者无法识别的分区,或者分区逻辑结构有错误,都可以完整的(包括逻辑错误)一起备份到目标设备上. 按文件转存意思是要对文件系统解释atch后,只按文件的方式提取到目标设备上.这个转存程序必须可以解释对应的文件系统,同时不会提取非文件数据.他的数据量也取

编写图片空间QTP脚本时的一点经验

我录制QTP脚本的思路是让能跑起来的都跑起来,不能跑起来的暂时不管,同时先采取固化脚本的思路写脚本(详细),完成后再加入参数化,让脚本灵活起来!呵呵! 以下是我编写图片空间时的一点经验! 经验1:用FireEvent方法处理TOP菜单中弹出的子菜单 Browser("淘宝网-店铺管理平台").Page("淘宝网-店铺管理平台").Link("素材管理(1)").Click Browser("淘宝网-店铺管理平台").Page(

win2008做群集时出现的问题的解决方法

在win2008做群集时出现"验证防火墙配置时出错,已添加了具有相同键的项"的问题 在做win2008群集时,我们一般习惯于先装完一台服务器然后Ghost到另外一台服务器来节约时间,恰恰是这个举动造成出现验证群集时出错,会提示"验证防火墙配置时出错 已添加了具有相同键的项",如下图: 该问题是由于两台服务器网卡的GUID号相同造成,需要在注册中进行修改 单击 开始,单击 运行,键入 regedit,然后单击 确定. 查找并删除以下注册表子项: HKEY_LOCAL_

使用Nunit+Access做单元测试时连接已经关闭

今天在使用Nunit+Access做单元测试时,本来正常的代码突然间不能正常通过在VS2005 Express中调试则可以正常通过,正在那闷中发现我打开了Access,结果关了Access代码则能正常通过了