JavaScript实现前端分页效果从零开始详解与常见问题解决方案

引言
在现代Web开发中,分页功能是数据展示的核心组件之一。无论是电商网站的商品列表、新闻网站的文章列表,还是后台管理系统的数据表格,分页都能有效提升用户体验和页面性能。本文将从零开始,详细讲解如何使用纯JavaScript实现前端分页效果,并提供常见问题的解决方案。
一、分页的基本概念
1.1 什么是分页?
分页是将大量数据分成若干页进行展示的技术。用户可以通过点击页码、上一页、下一页等控件来浏览不同页面的数据。
1.2 分页的核心要素
总数据量
:需要展示的全部数据条数
每页数据量
:每页显示的数据条数
当前页码
:用户当前查看的页面
总页数
:根据总数据量和每页数据量计算得出
分页控件
:页码按钮、上一页/下一页按钮等
二、基础实现:纯JavaScript分页
2.1 HTML结构准备
首先,我们需要准备基础的HTML结构:





JavaScript分页示例


JavaScript分页示例






2.2 JavaScript核心逻辑
创建一个名为
pagination.js
的文件,实现分页逻辑:
// pagination.js

// 模拟数据生成器 – 生成100条测试数据
function generateMockData(count = 100) {
const data = [];
for (let i = 1; i <= count; i++) { data.push({ id: i, name: `项目 ${i}`, description: `这是第 ${i} 个项目的详细描述`, date: new Date(2023, 0, i).toLocaleDateString('zh-CN'), status: i % 3 === 0 ? '已完成' : (i % 3 === 1 ? '进行中' : '待处理') }); } return data; } // 分页类 class Pagination { constructor(options = {}) { // 默认配置 this.config = { data: [], // 所有数据 pageSize: 10, // 每页显示数量 currentPage: 1, // 当前页码 containerId: 'dataContainer', // 数据容器ID paginationId: 'pagination', // 分页控件容器ID pageInfoId: 'pageInfo', // 页面信息容器ID ...options }; // 初始化 this.init(); } // 初始化方法 init() { this.validateConfig(); this.render(); } // 验证配置 validateConfig() { if (!this.config.data || !Array.isArray(this.config.data)) { throw new Error('数据必须是数组'); } if (this.config.pageSize <= 0) { throw new Error('每页数量必须大于0'); } if (this.config.currentPage <= 0) { throw new Error('当前页码必须大于0'); } } // 获取总页数 getTotalPages() { return Math.ceil(this.config.data.length / this.config.pageSize); } // 获取当前页数据 getCurrentPageData() { const start = (this.config.currentPage - 1) * this.config.pageSize; const end = start + this.config.pageSize; return this.config.data.slice(start, end); } // 渲染数据 renderData() { const container = document.getElementById(this.config.containerId); if (!container) return; const data = this.getCurrentPageData(); if (data.length === 0) { container.innerHTML = '

暂无数据

‘;
return;
}

let html = ”;
data.forEach(item => {
html += `

${item.name}

${item.description}

${item.date}

${item.status}

`;
});

container.innerHTML = html;
}

// 渲染分页控件
renderPagination() {
const container = document.getElementById(this.config.paginationId);
if (!container) return;

const totalPages = this.getTotalPages();

if (totalPages <= 1) { container.innerHTML = ''; return; } let html = ''; // 上一页按钮 const prevDisabled = this.config.currentPage === 1 ? 'disabled' : ''; html += ``;

// 页码按钮
const maxButtons = 5; // 最多显示5个页码按钮
let startPage = Math.max(1, this.config.currentPage – Math.floor(maxButtons / 2));
let endPage = Math.min(totalPages, startPage + maxButtons – 1);

// 调整起始页码,确保显示足够的页码
if (endPage – startPage + 1 < maxButtons) { startPage = Math.max(1, endPage - maxButtons + 1); } // 如果不是从第一页开始,显示第一页和省略号 if (startPage > 1) {
html += ``;
if (startPage > 2) {
html += `…`;
}
}

// 页码按钮
for (let i = startPage; i <= endPage; i++) { const activeClass = i === this.config.currentPage ? 'active' : ''; html += ``;
}

// 如果不是到最后一页,显示省略号和最后一页
if (endPage < totalPages) { if (endPage < totalPages - 1) { html += `…`;
}
html += ``;
}

// 下一页按钮
const nextDisabled = this.config.currentPage === totalPages ? ‘disabled’ : ”;
html += ``;

container.innerHTML = html;

// 为按钮添加事件监听
this.addPaginationEventListeners();
}

// 渲染页面信息
renderPageInfo() {
const container = document.getElementById(this.config.pageInfoId);
if (!container) return;

const totalPages = this.getTotalPages();
const start = (this.config.currentPage – 1) * this.config.pageSize + 1;
const end = Math.min(this.config.currentPage * this.config.pageSize, this.config.data.length);

container.textContent = `显示 ${start}-${end} 条,共 ${this.config.data.length} 条数据,第 ${this.config.currentPage}/${totalPages} 页`;
}

// 添加分页事件监听
addPaginationEventListeners() {
const container = document.getElementById(this.config.paginationId);

// 页码点击事件
container.querySelectorAll(‘button[data-page]’).forEach(btn => {
btn.addEventListener(‘click’, (e) => {
const page = parseInt(e.target.dataset.page);
this.goToPage(page);
});
});

// 上一页/下一页事件
const prevBtn = document.getElementById(‘prevPage’);
const nextBtn = document.getElementById(‘nextPage’);

if (prevBtn) {
prevBtn.addEventListener(‘click’, () => this.prevPage());
}

if (nextBtn) {
nextBtn.addEventListener(‘click’, () => this.nextPage());
}
}

// 跳转到指定页
goToPage(page) {
const totalPages = this.getTotalPages();

if (page < 1 || page > totalPages) {
console.warn(`页码 ${page} 超出范围 (1-${totalPages})`);
return;
}

this.config.currentPage = page;
this.render();
}

// 上一页
prevPage() {
if (this.config.currentPage > 1) {
this.goToPage(this.config.currentPage – 1);
}
}

// 下一页
nextPage() {
const totalPages = this.getTotalPages();
if (this.config.currentPage < totalPages) { this.goToPage(this.config.currentPage + 1); } } // 更新数据 updateData(newData) { if (!Array.isArray(newData)) { throw new Error('新数据必须是数组'); } this.config.data = newData; this.config.currentPage = 1; // 重置到第一页 this.render(); } // 更新每页数量 updatePageSize(newSize) { if (newSize <= 0) { throw new Error('每页数量必须大于0'); } this.config.pageSize = newSize; this.config.currentPage = 1; // 重置到第一页 this.render(); } // 渲染所有内容 render() { this.renderData(); this.renderPagination(); this.renderPageInfo(); } } // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', function() { // 生成测试数据 const mockData = generateMockData(100); // 创建分页实例 const pagination = new Pagination({ data: mockData, pageSize: 10, currentPage: 1 }); // 为应用设置按钮添加事件 const applyBtn = document.getElementById('applySettings'); if (applyBtn) { applyBtn.addEventListener('click', function() { const pageSizeInput = document.getElementById('pageSize'); const newSize = parseInt(pageSizeInput.value); if (newSize > 0 && newSize <= 50) { pagination.updatePageSize(newSize); } else { alert('请输入1-50之间的数字'); } }); } // 将分页实例暴露到全局,方便调试 window.pagination = pagination; }); 三、进阶功能实现 3.1 添加搜索功能 在实际应用中,我们经常需要结合搜索功能。以下是增强版的分页类,支持搜索: // enhanced-pagination.js class EnhancedPagination extends Pagination { constructor(options = {}) { super(options); // 搜索相关配置 this.searchConfig = { searchTerm: '', searchFields: ['name', 'description'], // 搜索字段 ...options.searchConfig }; // 原始数据备份(用于搜索) this.originalData = [...this.config.data]; } // 搜索方法 search(term) { this.searchConfig.searchTerm = term.toLowerCase().trim(); if (!term) { // 如果搜索词为空,恢复原始数据 this.config.data = [...this.originalData]; } else { // 执行搜索 this.config.data = this.originalData.filter(item => {
return this.searchConfig.searchFields.some(field => {
const value = item[field];
if (value) {
return value.toString().toLowerCase().includes(this.searchConfig.searchTerm);
}
return false;
});
});
}

// 重置到第一页
this.config.currentPage = 1;
this.render();
}

// 重写更新数据方法,同时更新原始数据
updateData(newData) {
super.updateData(newData);
this.originalData = […newData];
}
}

// 使用示例
document.addEventListener(‘DOMContentLoaded’, function() {
const mockData = generateMockData(100);

const enhancedPagination = new EnhancedPagination({
data: mockData,
pageSize: 10,
searchConfig: {
searchFields: [‘name’, ‘description’, ‘status’]
}
});

// 添加搜索框
const searchContainer = document.createElement(‘div’);
searchContainer.className = ‘controls’;
searchContainer.innerHTML = `



`;

// 插入到页面顶部
document.body.insertBefore(searchContainer, document.querySelector(‘.controls’));

// 搜索事件
document.getElementById(‘searchBtn’).addEventListener(‘click’, function() {
const term = document.getElementById(‘searchInput’).value;
enhancedPagination.search(term);
});

document.getElementById(‘searchInput’).addEventListener(‘keypress’, function(e) {
if (e.key === ‘Enter’) {
const term = this.value;
enhancedPagination.search(term);
}
});

document.getElementById(‘clearSearchBtn’).addEventListener(‘click’, function() {
document.getElementById(‘searchInput’).value = ”;
enhancedPagination.search(”);
});

window.enhancedPagination = enhancedPagination;
});
3.2 添加排序功能
// sortable-pagination.js

class SortablePagination extends EnhancedPagination {
constructor(options = {}) {
super(options);

// 排序配置
this.sortConfig = {
field: null,
direction: ‘asc’, // ‘asc’ 或 ‘desc’
…options.sortConfig
};
}

// 排序方法
sort(field, direction = ‘asc’) {
// 如果点击的是当前排序字段,则切换方向
if (this.sortConfig.field === field) {
this.sortConfig.direction = this.sortConfig.direction === ‘asc’ ? ‘desc’ : ‘asc’;
} else {
this.sortConfig.field = field;
this.sortConfig.direction = direction;
}

// 执行排序
this.config.data.sort((a, b) => {
let valA = a[field];
let valB = b[field];

// 处理数字类型
if (typeof valA === ‘number’ && typeof valB === ‘number’) {
return this.sortConfig.direction === ‘asc’ ? valA – valB : valB – valA;
}

// 处理字符串类型
valA = valA.toString().toLowerCase();
valB = valB.toString().toLowerCase();

if (valA < valB) { return this.sortConfig.direction === 'asc' ? -1 : 1; } if (valA > valB) {
return this.sortConfig.direction === ‘asc’ ? 1 : -1;
}
return 0;
});

this.render();
}

// 重写渲染数据方法,添加排序指示器
renderData() {
const container = document.getElementById(this.config.containerId);
if (!container) return;

const data = this.getCurrentPageData();

if (data.length === 0) {
container.innerHTML = ‘

暂无数据

‘;
return;
}

let html = ”;
data.forEach(item => {
html += `

${item.name}

${item.description}

${item.date}

${item.status}

`;
});

container.innerHTML = html;
}

// 重写渲染分页控件,添加排序控件
renderPagination() {
super.renderPagination();

// 添加排序控件到分页容器上方
const paginationContainer = document.getElementById(this.config.paginationId);
if (!paginationContainer) return;

// 检查是否已存在排序控件
let sortControls = document.getElementById(‘sortControls’);
if (!sortControls) {
sortControls = document.createElement(‘div’);
sortControls.id = ‘sortControls’;
sortControls.style.cssText = ‘display:flex; gap:10px; margin-bottom:10px; justify-content:center; flex-wrap:wrap;’;
paginationContainer.parentNode.insertBefore(sortControls, paginationContainer);
}

// 生成排序按钮
const fields = [‘id’, ‘name’, ‘date’, ‘status’];
let sortHtml = ‘排序:‘;

fields.forEach(field => {
const isActive = this.sortConfig.field === field;
const direction = isActive ? this.sortConfig.direction : ‘asc’;
const arrow = isActive ? (direction === ‘asc’ ? ‘↑’ : ‘↓’) : ”;

sortHtml += `

`;
});

sortControls.innerHTML = sortHtml;

// 添加排序事件
sortControls.querySelectorAll(‘button[data-sort-field]’).forEach(btn => {
btn.addEventListener(‘click’, (e) => {
const field = e.target.dataset.sortField;
const direction = e.target.dataset.sortDirection;
this.sort(field, direction);
});
});
}
}

// 使用示例
document.addEventListener(‘DOMContentLoaded’, function() {
const mockData = generateMockData(100);

const sortablePagination = new SortablePagination({
data: mockData,
pageSize: 10,
searchConfig: {
searchFields: [‘name’, ‘description’, ‘status’]
}
});

window.sortablePagination = sortablePagination;
});
四、常见问题与解决方案
4.1 性能问题
问题描述
:当数据量非常大(如10万+条)时,前端分页可能导致页面卡顿。
解决方案
:
虚拟滚动
:只渲染可视区域的数据
懒加载
:分页时只加载当前页数据
Web Workers
:将数据处理放到后台线程
// 虚拟滚动分页示例
class VirtualScrollPagination {
constructor(options = {}) {
this.config = {
data: [],
pageSize: 20,
containerHeight: 400, // 容器高度
itemHeight: 50, // 每项高度
…options
};

this.visibleStart = 0;
this.visibleEnd = 0;
this.scrollTop = 0;

this.init();
}

init() {
this.createContainer();
this.bindScrollEvent();
this.updateVisibleRange();
}

createContainer() {
const container = document.getElementById(this.config.containerId);
if (!container) return;

// 设置容器高度
container.style.height = this.config.containerHeight + ‘px’;
container.style.overflowY = ‘auto’;
container.style.position = ‘relative’;

// 创建占位元素
const placeholder = document.createElement(‘div’);
placeholder.id = ‘virtualPlaceholder’;
placeholder.style.height = (this.config.data.length * this.config.itemHeight) + ‘px’;
container.appendChild(placeholder);

// 创建可视区域容器
const viewport = document.createElement(‘div’);
viewport.id = ‘virtualViewport’;
viewport.style.position = ‘absolute’;
viewport.style.top = ‘0’;
viewport.style.left = ‘0’;
viewport.style.right = ‘0’;
container.appendChild(viewport);
}

bindScrollEvent() {
const container = document.getElementById(this.config.containerId);
if (!container) return;

container.addEventListener(‘scroll’, () => {
this.scrollTop = container.scrollTop;
this.updateVisibleRange();
});
}

updateVisibleRange() {
const containerHeight = this.config.containerHeight;
const itemHeight = this.config.itemHeight;

// 计算可见范围
const start = Math.floor(this.scrollTop / itemHeight);
const end = Math.ceil((this.scrollTop + containerHeight) / itemHeight);

// 添加缓冲区
const buffer = 5;
this.visibleStart = Math.max(0, start – buffer);
this.visibleEnd = Math.min(this.config.data.length, end + buffer);

this.renderVisibleItems();
}

renderVisibleItems() {
const viewport = document.getElementById(‘virtualViewport’);
if (!viewport) return;

// 清空现有内容
viewport.innerHTML = ”;

// 设置偏移
viewport.style.top = (this.visibleStart * this.config.itemHeight) + ‘px’;

// 渲染可见项
for (let i = this.visibleStart; i < this.visibleEnd; i++) { const item = this.config.data[i]; const div = document.createElement('div'); div.style.height = this.config.itemHeight + 'px'; div.style.borderBottom = '1px solid #eee'; div.style.padding = '10px'; div.style.boxSizing = 'border-box'; div.textContent = `${item.name} - ${item.description}`; viewport.appendChild(div); } } // 更新数据 updateData(newData) { this.config.data = newData; // 更新占位元素高度 const placeholder = document.getElementById('virtualPlaceholder'); if (placeholder) { placeholder.style.height = (this.config.data.length * this.config.itemHeight) + 'px'; } this.updateVisibleRange(); } } 4.2 内存泄漏问题 问题描述 :频繁创建和销毁DOM元素可能导致内存泄漏。 解决方案 : 事件委托 :使用事件委托减少事件监听器数量 对象复用 :复用DOM元素而非频繁创建 清理事件监听器 :在不需要时移除事件监听器 // 优化后的事件处理 class OptimizedPagination extends Pagination { constructor(options = {}) { super(options); // 使用事件委托 this.setupEventDelegation(); } setupEventDelegation() { const paginationContainer = document.getElementById(this.config.paginationId); if (!paginationContainer) return; // 使用事件委托处理所有分页按钮点击 paginationContainer.addEventListener('click', (e) => {
const target = e.target;

if (target.tagName === ‘BUTTON’) {
// 上一页/下一页
if (target.id === ‘prevPage’) {
this.prevPage();
} else if (target.id === ‘nextPage’) {
this.nextPage();
}
// 页码按钮
else if (target.dataset.page) {
const page = parseInt(target.dataset.page);
this.goToPage(page);
}
}
});
}

// 重写渲染方法,避免重复创建事件监听器
renderPagination() {
const container = document.getElementById(this.config.paginationId);
if (!container) return;

const totalPages = this.getTotalPages();

if (totalPages <= 1) { container.innerHTML = ''; return; } // 使用DocumentFragment减少DOM操作 const fragment = document.createDocumentFragment(); // 上一页按钮 const prevBtn = document.createElement('button'); prevBtn.id = 'prevPage'; prevBtn.textContent = '上一页'; if (this.config.currentPage === 1) { prevBtn.disabled = true; } fragment.appendChild(prevBtn); // 页码按钮逻辑(与之前相同,但使用fragment) const maxButtons = 5; let startPage = Math.max(1, this.config.currentPage - Math.floor(maxButtons / 2)); let endPage = Math.min(totalPages, startPage + maxButtons - 1); if (endPage - startPage + 1 < maxButtons) { startPage = Math.max(1, endPage - maxButtons + 1); } if (startPage > 1) {
const firstBtn = document.createElement(‘button’);
firstBtn.dataset.page = ‘1’;
firstBtn.textContent = ‘1’;
fragment.appendChild(firstBtn);

if (startPage > 2) {
const ellipsis = document.createElement(‘span’);
ellipsis.textContent = ‘…’;
ellipsis.style.padding = ‘8px’;
fragment.appendChild(ellipsis);
}
}

for (let i = startPage; i <= endPage; i++) { const btn = document.createElement('button'); btn.dataset.page = i; btn.textContent = i; if (i === this.config.currentPage) { btn.classList.add('active'); } fragment.appendChild(btn); } if (endPage < totalPages) { if (endPage < totalPages - 1) { const ellipsis = document.createElement('span'); ellipsis.textContent = '...'; ellipsis.style.padding = '8px'; fragment.appendChild(ellipsis); } const lastBtn = document.createElement('button'); lastBtn.dataset.page = totalPages; lastBtn.textContent = totalPages; fragment.appendChild(lastBtn); } // 下一页按钮 const nextBtn = document.createElement('button'); nextBtn.id = 'nextPage'; nextBtn.textContent = '下一页'; if (this.config.currentPage === totalPages) { nextBtn.disabled = true; } fragment.appendChild(nextBtn); // 一次性更新DOM container.innerHTML = ''; container.appendChild(fragment); } } 4.3 响应式设计问题 问题描述 :在移动设备上分页控件显示不全或布局错乱。 解决方案 : /* 响应式分页样式 */ @media (max-width: 768px) { .pagination { gap: 3px; } .pagination button { padding: 6px 8px; font-size: 12px; } .pagination span { padding: 6px; } /* 在小屏幕上只显示当前页和相邻页码 */ .pagination button[data-page] { display: none; } .pagination button[data-page].active, .pagination button[data-page].active + button[data-page], .pagination button[data-page].active ~ button[data-page]:first-of-type { display: inline-block; } /* 上一页/下一页按钮始终显示 */ .pagination button#prevPage, .pagination button#nextPage { display: inline-block; } } @media (max-width: 480px) { .controls { flex-direction: column; align-items: stretch; } .controls input { width: 100%; } .pagination { flex-wrap: wrap; } /* 移动端优化:减少页码显示数量 */ .pagination button[data-page] { display: none; } .pagination button[data-page].active { display: inline-block; } /* 显示省略号和最后一页 */ .pagination span, .pagination button[data-page]:last-child { display: inline-block; } } 4.4 数据更新问题 问题描述 :当数据动态变化时,分页状态可能不正确。 解决方案 : // 数据管理器 class DataManager { constructor() { this.data = []; this.subscribers = []; } // 订阅数据变化 subscribe(callback) { this.subscribers.push(callback); } // 通知订阅者 notify() { this.subscribers.forEach(callback => callback(this.data));
}

// 更新数据
updateData(newData) {
this.data = newData;
this.notify();
}

// 添加数据
addData(item) {
this.data.push(item);
this.notify();
}

// 删除数据
removeData(id) {
this.data = this.data.filter(item => item.id !== id);
this.notify();
}
}

// 使用数据管理器的分页
class DataAwarePagination extends Pagination {
constructor(options = {}) {
super(options);

this.dataManager = options.dataManager || new DataManager();
this.dataManager.subscribe((newData) => {
this.updateData(newData);
});
}

// 重写更新数据方法
updateData(newData) {
// 保存当前页码
const currentPage = this.config.currentPage;

// 更新数据
super.updateData(newData);

// 尝试保持在当前页
const totalPages = this.getTotalPages();
if (currentPage <= totalPages) { this.config.currentPage = currentPage; this.render(); } } } 五、完整示例与最佳实践 5.1 完整的分页组件 // complete-pagination.js class CompletePagination { constructor(options = {}) { // 默认配置 this.config = { data: [], pageSize: 10, currentPage: 1, containerId: 'pagination-container', showSearch: true, showSort: true, showPageSizeSelector: true, maxVisiblePages: 5, ...options }; // 状态管理 this.state = { searchTerm: '', sortField: null, sortDirection: 'asc', filteredData: [] }; // DOM缓存 this.dom = {}; this.init(); } init() { this.validateConfig(); this.setupDOM(); this.bindEvents(); this.render(); } validateConfig() { if (!this.config.data || !Array.isArray(this.config.data)) { throw new Error('数据必须是数组'); } if (this.config.pageSize <= 0) { throw new Error('每页数量必须大于0'); } } setupDOM() { const container = document.getElementById(this.config.containerId); if (!container) { throw new Error(`容器元素 #${this.config.containerId} 不存在`); } // 创建HTML结构 container.innerHTML = `

${this.config.showSearch ? `

` : ”}

${this.config.showPageSizeSelector ? `


` : ”}

${this.config.showSort ? `

排序:


` : ”}

`;

// 缓存DOM元素
this.dom.container = container;
this.dom.dataContainer = container.querySelector(‘#data-container’);
this.dom.infoContainer = container.querySelector(‘#pagination-info’);
this.dom.buttonsContainer = container.querySelector(‘#pagination-buttons’);

if (this.config.showSearch) {
this.dom.searchInput = container.querySelector(‘.search-input’);
this.dom.searchBtn = container.querySelector(‘.search-btn’);
this.dom.clearBtn = container.querySelector(‘.clear-btn’);
}

if (this.config.showPageSizeSelector) {
this.dom.pageSizeSelect = container.querySelector(‘.page-size-select’);
}

if (this.config.showSort) {
this.dom.sortButtons = container.querySelectorAll(‘.sort-btn’);
}
}

bindEvents() {
// 搜索事件
if (this.dom.searchBtn) {
this.dom.searchBtn.addEventListener(‘click’, () => this.handleSearch());
}

if (this.dom.searchInput) {
this.dom.searchInput.addEventListener(‘keypress’, (e) => {
if (e.key === ‘Enter’) this.handleSearch();
});
}

if (this.dom.clearBtn) {
this.dom.clearBtn.addEventListener(‘click’, () => this.handleClearSearch());
}

// 每页数量选择事件
if (this.dom.pageSizeSelect) {
this.dom.pageSizeSelect.addEventListener(‘change’, (e) => {
this.config.pageSize = parseInt(e.target.value);
this.config.currentPage = 1;
this.render();
});
}

// 排序事件
if (this.dom.sortButtons) {
this.dom.sortButtons.forEach(btn => {
btn.addEventListener(‘click’, (e) => {
const field = e.target.dataset.field;
this.handleSort(field);
});
});
}

// 分页按钮事件(使用事件委托)
if (this.dom.buttonsContainer) {
this.dom.buttonsContainer.addEventListener(‘click’, (e) => {
const target = e.target;
if (target.tagName === ‘BUTTON’) {
if (target.id === ‘prev-btn’) {
this.prevPage();
} else if (target.id === ‘next-btn’) {
this.nextPage();
} else if (target.dataset.page) {
const page = parseInt(target.dataset.page);
this.goToPage(page);
}
}
});
}
}

handleSearch() {
const term = this.dom.searchInput.value.trim().toLowerCase();
this.state.searchTerm = term;
this.config.currentPage = 1;
this.render();
}

handleClearSearch() {
this.dom.searchInput.value = ”;
this.state.searchTerm = ”;
this.config.currentPage = 1;
this.render();
}

handleSort(field) {
if (this.state.sortField === field) {
this.state.sortDirection = this.state.sortDirection === ‘asc’ ? ‘desc’ : ‘asc’;
} else {
this.state.sortField = field;
this.state.sortDirection = ‘asc’;
}
this.render();
}

// 数据处理
processData() {
let data = […this.config.data];

// 搜索过滤
if (this.state.searchTerm) {
data = data.filter(item => {
return Object.values(item).some(value =>
value.toString().toLowerCase().includes(this.state.searchTerm)
);
});
}

// 排序
if (this.state.sortField) {
data.sort((a, b) => {
let valA = a[this.state.sortField];
let valB = b[this.state.sortField];

if (typeof valA === ‘number’ && typeof valB === ‘number’) {
return this.state.sortDirection === ‘asc’ ? valA – valB : valB – valA;
}

valA = valA.toString().toLowerCase();
valB = valB.toString().toLowerCase();

if (valA < valB) return this.state.sortDirection === 'asc' ? -1 : 1; if (valA > valB) return this.state.sortDirection === ‘asc’ ? 1 : -1;
return 0;
});
}

this.state.filteredData = data;
return data;
}

// 获取当前页数据
getCurrentPageData() {
const data = this.processData();
const start = (this.config.currentPage – 1) * this.config.pageSize;
const end = start + this.config.pageSize;
return data.slice(start, end);
}

// 获取总页数
getTotalPages() {
const data = this.processData();
return Math.ceil(data.length / this.config.pageSize);
}

// 渲染数据
renderData() {
const data = this.getCurrentPageData();

if (data.length === 0) {
this.dom.dataContainer.innerHTML = ‘

暂无数据

‘;
return;
}

let html = ”;
data.forEach(item => {
html += `

${item.name}

${item.description}

${item.date}

${item.status}

`;
});

this.dom.dataContainer.innerHTML = html;
}

// 渲染分页信息
renderInfo() {
const data = this.processData();
const totalPages = this.getTotalPages();
const start = (this.config.currentPage – 1) * this.config.pageSize + 1;
const end = Math.min(this.config.currentPage * this.config.pageSize, data.length);

let info = `显示 ${start}-${end} 条,共 ${data.length} 条数据`;

if (totalPages > 1) {
info += `,第 ${this.config.currentPage}/${totalPages} 页`;
}

// 显示搜索和排序状态
if (this.state.searchTerm) {
info += ` | 搜索:”${this.state.searchTerm}”`;
}

if (this.state.sortField) {
const direction = this.state.sortDirection === ‘asc’ ? ‘↑’ : ‘↓’;
info += ` | 排序:${this.state.sortField} ${direction}`;
}

this.dom.infoContainer.textContent = info;
}

// 渲染分页按钮
renderButtons() {
const totalPages = this.getTotalPages();

if (totalPages <= 1) { this.dom.buttonsContainer.innerHTML = ''; return; } let html = ''; // 上一页 const prevDisabled = this.config.currentPage === 1 ? 'disabled' : ''; html += ``;

// 页码按钮
const maxButtons = this.config.maxVisiblePages;
let startPage = Math.max(1, this.config.currentPage – Math.floor(maxButtons / 2));
let endPage = Math.min(totalPages, startPage + maxButtons – 1);

if (endPage – startPage + 1 < maxButtons) { startPage = Math.max(1, endPage - maxButtons + 1); } if (startPage > 1) {
html += ``;
if (startPage > 2) {
html += `…`;
}
}

for (let i = startPage; i <= endPage; i++) { const activeClass = i === this.config.currentPage ? 'active' : ''; html += ``;
}

if (endPage < totalPages) { if (endPage < totalPages - 1) { html += `…`;
}
html += ``;
}

// 下一页
const nextDisabled = this.config.currentPage === totalPages ? ‘disabled’ : ”;
html += ``;

this.dom.buttonsContainer.innerHTML = html;
}

// 分页操作
goToPage(page) {
const totalPages = this.getTotalPages();
if (page < 1 || page > totalPages) return;

this.config.currentPage = page;
this.render();
}

prevPage() {
if (this.config.currentPage > 1) {
this.goToPage(this.config.currentPage – 1);
}
}

nextPage() {
const totalPages = this.getTotalPages();
if (this.config.currentPage < totalPages) { this.goToPage(this.config.currentPage + 1); } } // 更新数据 updateData(newData) { this.config.data = newData; this.config.currentPage = 1; this.render(); } // 渲染所有内容 render() { this.renderData(); this.renderInfo(); this.renderButtons(); } } // 使用示例 document.addEventListener('DOMContentLoaded', function() { // 生成测试数据 function generateData(count = 100) { const data = []; for (let i = 1; i <= count; i++) { data.push({ id: i, name: `项目 ${i}`, description: `这是第 ${i} 个项目的详细描述,包含更多信息`, date: new Date(2023, 0, i).toLocaleDateString('zh-CN'), status: i % 3 === 0 ? '已完成' : (i % 3 === 1 ? '进行中' : '待处理') }); } return data; } // 创建分页实例 const pagination = new CompletePagination({ data: generateData(100), pageSize: 10, containerId: 'pagination-container', showSearch: true, showSort: true, showPageSizeSelector: true, maxVisiblePages: 5 }); // 暴露到全局方便调试 window.pagination = pagination; }); 5.2 最佳实践总结 性能优化 : 使用事件委托减少事件监听器 使用DocumentFragment减少DOM操作 对于大数据量考虑虚拟滚动 可维护性 : 使用类封装分页逻辑 配置与逻辑分离 提供清晰的API 用户体验 : 提供搜索和排序功能 响应式设计适配不同设备 清晰的页面信息展示 错误处理 : 验证输入参数 处理边界情况 提供友好的错误提示 六、总结 本文从零开始详细讲解了JavaScript实现前端分页的完整过程,包括: 基础实现 :核心分页逻辑和HTML结构 进阶功能 :搜索、排序、虚拟滚动等 常见问题 :性能、内存泄漏、响应式设计等 完整示例 :一个功能完整的分页组件 通过这些内容,你可以根据实际项目需求选择合适的实现方式。对于简单项目,基础分页类就足够了;对于复杂项目,可以考虑使用完整的分页组件或结合框架(如Vue、React)实现。 记住,好的分页组件应该具备: 清晰的逻辑、良好的性能、优秀的用户体验 。在实际开发中,根据数据量、用户需求和项目复杂度来选择合适的实现方案。