把这页想成"图书馆排书架":35 本书一口气全摊在桌上没法看,于是规定每层摆 5 本(pageSize),总共有几层(totalPages)、你现在站在第几层(currentPage)。真正的难点不是"切页"这一下,而是那些容易被忽略的边界情况:把某层最后一本抽走了、在搜索框输入了关键词、页码多到一屏放不下要省略号。这页三个数 page / pageSize / total 背下来,分页就会一半。
| ID | 姓名 | 年龄 | 操作 |
|---|
试试:① 删掉当前页最后一条数据 → 自动回退上一页;② 搜索后页码自动回到第 1 页;③ 首页、中间页、尾页的省略号位置。
let currentPage = 1; // 当前页码(从第 1 页开始,不是 0)→ 你现在翻到第几页 const pageSize = 5; // 每页几条 → 一页固定摆几条 let total = allData.length; // 总数据条数 → 一共有多少条 let totalPages = Math.ceil(total / pageSize); // 总页数,向上取整 → 一共能分成几页 // 截取当前页数据:含头不含尾(slice 的特性)→ slice 包头不包尾 const startIndex = (currentPage - 1) * pageSize; // 本页第一条的下标 → 从第几条开始 const endIndex = currentPage * pageSize; // 本页最后一条的下一个下标 → 取到第几条为止 const currentData = allData.slice(startIndex, endIndex); // 把这一页的几条切出来
先理解下标怎么算。每页 5 条:第 1 页取 0~5,第 2 页取 5~10,第 3 页取 10~15。所以"本页第一条下标" = (currentPage - 1) × pageSize。为什么总页数要用 Math.ceil(向上取整)?因为 25 条 ÷ 每页 10 条 = 2.5 页,你不能说"只有 2.5 页",最后半页那 5 条也得有个地方放,所以向上取成 3 页。slice 是左含右不含,所以 endIndex 直接写 currentPage × pageSize 就行,千万别写成 (currentPage+1)×pageSize,那样每页就多切一条了。
function renderPagination() { // 重画底部那一排页码
let html = ""; // 先准备一个空字符串攒 HTML
const total = totalPages,
current = currentPage; // 把总页数、当前页取出来省事
const maxShow = 7; // 最多显示 7 个位置,多了就用省略号代替 → 一屏最多放 7 个格子
let pages = []; // 先把"要显示哪些"装进这个数组
if (total <= maxShow) { // 页数很少(7 页以内)
for (let i = 1; i <= total; i++) pages.push(i); // 页数少,干脆全显示 → 1、2、3...全列出来
} else { // 页数太多,要省略号
pages.push(1); // 1. 永远先放"首页" → 第一个永远是 1
let midStart = current - 2,
midEnd = current + 2; // 2. 当前页左右各留 2 页 → 当前页前后各显示 2 个
if (midStart <= 2) { // 当前页太靠左边了
midStart = 2; // 中间段从 2 开始
midEnd = Math.min(total - 1, midStart + 4); // 右边别超过尾页前一个
}
if (midEnd >= total - 1) { // 当前页太靠右边了
midEnd = total - 1; // 中间段收到尾页前一个
midStart = Math.max(2, midEnd - 4); // 左边别小于 2
}
if (midStart > 2) pages.push("..."); // 3. 左边和首页之间塞省略号 → 左边空出一截就打 ...
for (let i = midStart; i <= midEnd; i++) pages.push(i); // 4. 中间连续页码 → 把这几页都放进去
if (midEnd < total - 1) pages.push("..."); // 5. 右边和尾页之间塞省略号 → 右边空出一截就打 ...
pages.push(total); // 6. 永远最后放"尾页" → 最后一个永远是总页数
// 7. 去重:防止首页和中间段撞车,比如 current=1 时会重复
pages = [...new Set(pages)]; // 用 Set 去重,避免出现两个 1
}
// 拼 HTML:当前页加 active 高亮,省略号用 span(不可点)
html += current > 1 ? `<button data-page="${current - 1}">上一页</button>` : ""; // 不是首页才显示"上一页"
pages.forEach(p => { // 一个一个把页码拼成按钮
html += p === "..." ? `<span class="dot">...</span>` : // 是省略号就画个不可点的点
`<button class="${p === current ? "active" : ""}" data-page="${p}">${p}</button>`; // 数字就画按钮,当前页加高亮
});
html += current < total ? `<button data-page="${current + 1}">下一页</button>` : ""; // 不是尾页才显示"下一页"
document.getElementById("pagination").innerHTML = html; // 整排塞进页面
}
这一段的思路是:先把"要显示哪些东西"装进一个数组(可能是数字,也可能是 "..."),再统一渲染,比边判断边拼字符串清楚得多。首尾永远放 1 和 total,中间放当前页附近的几页,放不下的就用 "..." 占位。最后用 new Set 去重——因为当前页靠近第 1 页时,中间段会和"首页"撞成同一个数,不去重就会出现两个"1"。
// 删除后:先更新 total 和 totalPages → 数据少了,总数和总页数要重算
total = allUsers.length; // 现在还剩几条
totalPages = Math.ceil(total / pageSize); // 还能分成几页
// 再检查:当前页是不是被删空了?而且不是第 1 页 → 就回退一页
const currentPageData = allUsers.slice((currentPage - 1) * pageSize, currentPage * pageSize); // 把这页再切出来看看
if (currentPageData.length === 0 && currentPage > 1) { // 这页一条都没剩,且不是第 1 页
currentPage--; // ★ 关键:页码减一 → 退回到上一页
}
renderPageData(currentPage); // 重画表格数据 → 把上一页的数据画出来
renderPagination; // 重画页码按钮 → 页码高亮也跟着退回去
举个例子:你在第 3 页,把这页 5 条全删了,第 3 页就空了。如果不处理,页面上表格一片空白,但页码还亮着"3",用户会以为数据丢了。正确做法:判断"本页数据为空"且"不是第 1 页"两个条件同时成立,就 currentPage--。为什么要加"不是第 1 页"?因为如果本来就在第 1 页,再减一就变成第 0 页了,那是非法页码。回退后必须同时重画"表格"和"页码"两处,漏一处就会错位。
$("#searchBtn").click(function() { // 点"搜索"按钮
const kw = $("#kw").val.trim(); // 取输入框里的关键词,去掉两头空格
// ★ 必须基于【原始备份】originalUsers 筛选,不能在已经筛过的结果上接着筛
allUsers = kw // 有关键词就筛,没关键词就全部
? originalUsers.filter(u => u.name.includes(kw)) // 从原始数据里挑名字含关键词的
: [...originalUsers]; // 没关键词:把原始数据整个复制一份
total = allUsers.length; // 筛完还剩几条
totalPages = Math.ceil(total / pageSize); // 重算总页数
currentPage = 1; // ★ 搜完一律回第 1 页 → 别停在原来那页
renderPageData(currentPage); // 重画表格
renderPagination; // 重画页码
});
// 重置:三步缺一不可
$("#resetBtn").click(function() { // 点"重置"按钮
$("#kw").val(""); // ① 清空输入框
allUsers = [...originalUsers]; // ② 数据恢复原样 → 从原始备份重新复制一份
currentPage = 1; // ③ 页码也回 1
renderPageData(currentPage); // 重画表格
renderPagination; // 重画页码
});
两个新手必踩的坑:
① 筛选必须基于 originalUsers 备份。如果你直接在 allUsers 上反复 filter,第一次筛剩 20 条,第二次在这 20 条上再筛,结果会越筛越少,像滚雪球一样不可控。永远是"从原始数据重新算"。
② 搜完必须 currentPage = 1。设想你之前翻到了第 5 页,然后输入一个关键词,结果只剩 2 条(只有 1 页),这时 currentPage 还是 5,但 totalPages 已经是 1 了,你就停在一个不存在的页码上,表格空白。
重置按钮就是"清空输入框 + 恢复原始数据 + 页码回 1"三件套。
| 前端分页(客户端) | 后端分页(服务端) |
|---|---|
// 一次性把所有数据拿回浏览器 → 数据全在本地 // 翻页时前端用 slice 自己切 → 不找服务器,自己切 // 翻页不发网络请求,秒切,体验好 → 切页飞快 // 适合 < 1000 条的小数据 → 数据少时这么做 |
// 每次翻页都请求一次接口 → 翻一页就找一次服务器
$.ajax({ // 跟后端要数据
url: "/api/users", // 数据接口地址
data: { page, pageSize }, // 告诉后端:我要第几页、每页几条
success: res => { // 后端把这页数据送回来
renderPageData(res.data.list); // 后端只返回当前这一页 → 直接画这页
totalPages = Math.ceil(res.data.total / pageSize); // 后端还告诉了总条数,据此算总页数
}
});
// 适合几万、几十万条的海量数据 → 数据多时这么做,别一次全拉下来
|
记一句话:数据少用前端切(快),数据多用后端查(轻)。本页演示是前端分页,因为我们把 35 条数据直接写死在代码里了;真实项目动辄几万条,不可能一次全拉下来,就得后端分页——前端把"第几页、每页几条"发给接口,后端只返回那一页的数据。
① 可能在什么地方用:后台管理系统的用户列表、订单列表、商品列表;电商商品搜索结果翻页;论坛帖子列表——凡是"数据超过一屏"的表格,都要分页。
② 常见的问题:删掉最后一条后表格空白(忘了 currentPage-- 回退);搜索后停在不存在的页码(忘了 currentPage = 1);页码出现两个"1"或"..."重复(忘了用 Set 去重);越筛越少(在已筛结果上接着筛);slice 切多了或切少了(startIndex/endIndex 公式写错)。
③ 解决思路:在控制台把 total、totalPages、currentPage 打出来,对照着看:删完数据后这三个数变了没?搜完 currentPage 是不是回到 1 了?手动算一下第 3 页的 startIndex 应该是 10,slice 出来的数组长度对不对。边界场景(首页、尾页、删空、搜空)挨个点一遍,bug 基本都藏在这几个角落。