分页就是数据太多分几页显示,每页显示固定条数。
核心三步:
① 算总页数——Math.ceil(总数 / 每页条数)
② 切片数据——slice((当前页-1)*每页, 当前页*每页)
③ 渲染分页按钮——上一页/页码/下一页
坑:当前页不能小于 1 不能大于总页数、切片从 0 开始、页码按钮要高亮当前页。
| ID | 姓名 | 年龄 | 操作 |
|---|
试试:① 删掉当前页最后一条数据 → 自动回退上一页;② 搜索后页码自动回到第 1 页;③ 首页、中间页、尾页的省略号位置。
// 当前页码(从第 1 页开始,不是 0)→ 你现在翻到第几页 let currentPage = 1; // 每页几条 → 一页固定摆几条 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);
先理解下标怎么算,分四步:
1. 每页 5 条时各页取哪些:第 1 页取 0~5,第 2 页取 5~10,第 3 页取 10~15。
2. 本页第一条下标怎么算: = (currentPage - 1) × pageSize。
3. 为什么总页数用 Math.ceil(向上取整):25 条 ÷ 每页 10 条 = 2.5 页,你不能说"只有 2.5 页",最后半页那 5 条也得有个地方放,所以向上取成 3 页。
4. slice 左含右不含:所以 endIndex 直接写 currentPage × pageSize 就行,千万别写成 (currentPage+1)×pageSize,那样每页就多切一条了。
function renderPagination() {
// 先准备空字符串攒 HTML
let html = "";
// 把总页数、当前页取出来省事
const total = totalPages,
current = currentPage;
// 最多显示 7 个位置
const maxShow = 7;
// 先把要显示哪些装进数组
let pages = [];
if (total <= maxShow) {
// 页数少就全列出来
for (let i = 1; i <= total; i++) pages.push(i);
} else {
// 数字画按钮,当前页高亮
// 首页永远放 1
pages.push(1);
// 当前页左右各留 2 页
let midStart = current - 2,
midEnd = current + 2;
if (midStart <= 2) {
// 当前页太靠左
// 中间段从 2 开始
midStart = 2;
// 右边别超尾页前一个
midEnd = Math.min(total - 1, midStart + 4);
}
if (midEnd >= total - 1) {
// 当前页太靠右
// 中间段收到尾页前一个
midEnd = total - 1;
// 左边别小于 2
midStart = Math.max(2, midEnd - 4);
}
// 左边空出一截就打 ...
if (midStart > 2) pages.push("...");
// 中间连续页码
for (let i = midStart; i <= midEnd; i++) pages.push(i);
// 右边空出一截就打 ...
if (midEnd < total - 1) pages.push("...");
// 尾页永远放总页数
pages.push(total);
// 去重,避免两个 1
pages = Array.from(new Set(pages));
}
// 不是首页才显示上一页
html += current > 1 ? '<button data-page="' + (current - 1) + '">上一页</button>' : '';
// 一个一个拼页码按钮
pages.forEach(function(p) {
if (p === "...") {
// 省略号画个不可点的点
html += '...';
} else {
// 当前页加高亮
let active = (p === current ? "active" : "");
html += '';
}
});
// 不是尾页才显示下一页
html += current < total ? '<button data-page="' + (current + 1) + '">下一页</button>' : '';
// 整排塞进页面
$("#pagination").html(html);
}
这一段分四步:
1. 先把要显示哪些装进数组:可能是数字,也可能是 "...",再统一渲染,比边判断边拼字符串清楚得多。
2. 首尾永远放 1 和 total:中间放当前页附近的几页。
3. 放不下的用 "..." 占位:页太多时中间省略。
4. 最后用 new Set 去重:因为当前页靠近第 1 页时,中间段会和"首页"撞成同一个数,不去重就会出现两个"1"。
// 现在还剩几条
total = allUsers.length;
// 还能分成几页
totalPages = Math.ceil(total / pageSize);
const currentPageData = allUsers.slice((currentPage - 1) * pageSize, currentPage * pageSize);
// 这页空了,且不是第 1 页
if (currentPageData.length === 0 && currentPage > 1) {
// ★ 关键:页码减一
currentPage--;
}
// 重画表格数据
renderPageData(currentPage);
// 重画页码按钮
renderPagination();
举个例子,分四步理解:
1. 问题场景:你在第 3 页,把这页 5 条全删了,第 3 页就空了,表格一片空白但页码还亮着"3",用户以为数据丢了。
2. 正确做法:判断"本页数据为空"且"不是第 1 页"两个条件同时成立,就 currentPage--。
3. 为什么要加"不是第 1 页":因为如果本来就在第 1 页,再减一就变成第 0 页了,那是非法页码。
4. 回退后必须同时重画"表格"和"页码"两处:漏一处就会错位。
$("#searchBtn").click(function () {
// 取关键词,去掉两头空格
const kw = $("#kw").val().trim();
// ★ 必须基于原始备份 originalUsers 筛选,不能在结果上接着筛
if (kw) {
allUsers = originalUsers.filter(function (u) {
return u.name.includes(kw);
});
} else {
allUsers = originalUsers.slice();
}
// 筛完还剩几条
total = allUsers.length;
// 重算总页数
totalPages = Math.ceil(total / pageSize);
// 搜完回第 1 页
currentPage = 1;
// 重画表格
renderPageData(currentPage);
// 重画页码
renderPagination();
});
$("#resetBtn").click(function () {
// ① 清空输入框
$("#kw").val("");
// ② 数据恢复原样
allUsers = originalUsers.slice();
// ③ 页码回 1
currentPage = 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 },
// 后端把这页数据送回来(普通 function)
success: function (res) {
// 后端只返回当前这一页 → 直接画这页
renderPageData(res.data.list);
// 后端还告诉了总条数,据此算总页数
totalPages = Math.ceil(res.data.total / pageSize);
}
});
// 适合几万、几十万条的海量数据 → 数据多时这么做,别一次全拉下来
|
记一句话:数据少用前端切(快),数据多用后端查(轻)。分两种情况:
1. 前端分页(数据少):本页演示就是,把 35 条数据直接写死在代码里,翻页时用 slice 自己切,不找服务器。
2. 后端分页(数据多):真实项目动辄几万条,不可能一次全拉下来。前端把"第几页、每页几条"发给接口,后端只返回那一页的数据。
① 可能在什么地方用:后台管理系统的用户列表、订单列表、商品列表;电商商品搜索结果翻页;论坛帖子列表——凡是"数据超过一屏"的表格,都要分页。
② 常见的问题:删掉最后一条后表格空白(忘了 currentPage-- 回退);搜索后停在不存在的页码(忘了 currentPage = 1);页码出现两个"1"或"..."重复(忘了用 Set 去重);越筛越少(在已筛结果上接着筛);slice 切多了或切少了(startIndex/endIndex 公式写错)。
③ 解决思路:在控制台把 total、totalPages、currentPage 打出来,对照着看:删完数据后这三个数变了没?搜完 currentPage 是不是回到 1 了?手动算一下第 3 页的 startIndex 应该是 10,slice 出来的数组长度对不对。边界场景(首页、尾页、删空、搜空)挨个点一遍,bug 基本都藏在这几个角落。
| API | 作用 | 参数怎么传 | 返回/结果 | 代码示例 |
|---|---|---|---|---|
| slice | 从数组里切出当前页那几条 | (起,止) 左含右不含 | 新数组 | let data = allUsers.slice(0, 5); |
| Math.ceil | 算出一共几页 | (总数/每页条数) | 向上取整的页数 | let totalPages = Math.ceil(total / 5); |
| splice | 删掉表格里那一条 | (下标, 1) | 删完的数组 | allUsers.splice(2, 1); |
| findIndex | 按 id 找到是第几项 | (条件函数) | 下标,找不到 -1 | let idx = allUsers.findIndex(function(u) {
return u.id===id;
}); |
| parseInt | 把页码字符串转成数字 | ("5", 10) | 数字 5 | let page = parseInt(btn.dataset.page, 10); |