如何优化 PHP 和 Vanilla JavaScript 网页的加载与渲染速度?
要减少页面延迟,先确定时间花在服务器处理、资源下载,还是浏览器执行与渲染上,再针对瓶颈优化。使用 PHP Native 或 Vanilla JavaScript 本身不代表页面会慢,也不必为了性能先更换框架。
normalize.css 主要用于减少浏览器默认样式的差异,不能作为提升渲染速度的通用方案。目前没有请求耗时、性能记录或相关代码,还无法判断具体瓶颈。
先判断页面慢在哪个阶段
在浏览器开发者工具中,分别检查首次打开页面和一次动态更新操作。
| 现象 | 优先检查 | 可能的优化方向 |
|---|---|---|
| HTML 或接口很久才开始返回 | Network 中的等待时间、服务端日志 | PHP 执行、数据库查询、外部接口 |
| 响应开始较快,但资源下载耗时长 | 文件大小、请求瀑布图 | 压缩、图片尺寸、缓存、资源数量 |
| 数据已经返回,界面仍卡顿 | Performance 中的主线程记录 | JavaScript 长任务、DOM 操作、布局计算 |
| 内容出现后不断移动 | 元素尺寸和布局变化 | 图片尺寸、占位区域、字体加载 |
TTFB 包含网络往返和服务端等待等因素,不能直接当作 PHP 的执行时间。要定位后端问题,还需要分别记录数据库查询、外部请求和主要业务逻辑的耗时。
冷缓存和热缓存都要测试,也要适当模拟较慢的网络和较弱的设备。只看自己电脑上的刷新速度,容易漏掉真实用户遇到的问题。
PHP 端:缩短生成响应的时间
优先检查以下几项:
- 数据库查询:避免循环中逐条查询关联数据的 N+1 问题,只读取需要的字段和记录。对耗时查询查看执行计划,再判断是否需要调整索引或查询结构。
- 返回的数据量:列表采用分页或按需加载,避免首次访问就查询、序列化并返回全部数据。
- 外部调用:检查是否串行等待多个远程服务,并设置合理的超时。邮件发送、报表生成等不影响当前页面结果的任务,可以交给后台任务处理。
- 缓存:对重复计算、允许短暂过期的数据使用缓存,并明确缓存失效方式。包含用户身份或权限差异的响应需要正确隔离,不能直接放入共享缓存。
- 运行环境:在适用的部署环境中检查 OPcache 是否启用,以减少重复解析和编译 PHP 脚本的开销。它不能替代数据库和业务逻辑优化。
如果应用使用 PHP 默认的文件型 session,还要留意 session 锁:同一会话的并发请求可能互相等待。确认后续不再修改 session 后,可以调用 session_write_close() 提前释放锁;调用之后,不要继续依赖对 $_SESSION 的修改来保存数据。
首屏需要且能快速取得的数据,可以直接由 PHP 输出到 HTML,减少浏览器拿到页面后再请求一次数据的等待。代价是这部分查询会计入 HTML 的响应时间,应根据实际测量决定。
JavaScript 端:异步请求,并控制渲染工作量
使用异步 fetch() 可以避免同步网络请求阻塞主线程,但响应后的 JSON 解析、数据加工和 DOM 更新仍有成本。接口返回几万条记录时,即使网络请求是异步的,页面也可能卡顿。
下面的示例包括外部脚本延后执行、加载状态、错误处理和集中插入 DOM。示例假设 /api/items.php 返回形如 [{"name":"示例条目"}] 的 JSON 数组,实际项目需要替换接口地址和字段。
<p id="status" role="status">正在加载列表……</p>
<ul id="items" aria-busy="true"></ul>
<script src="/app.js" defer></script>
app.js:
const list = document.querySelector('#items');
const status = document.querySelector('#status');
async function loadItems() {
try {
const response = await fetch('/api/items.php');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const items = await response.json();
if (!Array.isArray(items)) {
throw new Error('Expected an array');
}
const fragment = document.createDocumentFragment();
for (const item of items) {
const li = document.createElement('li');
li.textContent = String(item.name ?? '');
fragment.appendChild(li);
}
list.replaceChildren(fragment);
status.textContent = items.length === 0 ? '暂无数据。' : '';
} catch (error) {
status.textContent = '列表加载失败,请稍后重试。';
console.error(error);
} finally {
list.setAttribute('aria-busy', 'false');
}
}
loadItems();
外部经典脚本使用 defer 后,可以在 HTML 解析期间下载,并在解析完成后执行。这能减少脚本对 HTML 解析的阻塞,但脚本执行时仍可能占用主线程。
示例把节点准备好后再插入页面,也没有把接口字符串直接拼成 HTML。不过,DocumentFragment 不保证所有场景都更快,也不能解决超大列表的渲染成本。数据量大时,优先分页;确实需要长列表连续滚动时,再考虑虚拟列表。
还应避免在循环中反复修改样式后立即读取 offsetHeight、getBoundingClientRect() 等布局信息。尽量集中读取,再集中写入,减少同步布局计算。动画更新可以使用 requestAnimationFrame(),但不要把大量计算塞进同一个回调。
减少资源加载与布局开销
可以检查以下资源问题:
- 图片是否远大于实际显示尺寸;是否为图片设置了
width、height或合适的宽高比。 - 屏幕外图片是否适合使用
loading="lazy";首屏关键图片则不应机械地延迟加载。 - CSS、JavaScript 是否包含大量当前页面用不到的内容,非关键功能能否按需加载。
- HTML、CSS、JavaScript 和 JSON 是否在服务器支持的情况下启用合适的传输压缩。
- 静态资源是否配置了合理缓存;长期缓存的资源是否通过文件名或 URL 版本变化更新。
不要为了减少 CSS 阻塞就直接异步加载全部样式。首屏依赖的样式如果到达过晚,可能产生样式闪烁和布局跳动。
normalize.css 是否有必要
是否使用 normalize.css,取决于项目对默认样式和浏览器一致性的要求。
CSS reset 通常会清除一部分默认样式;normalize.css 则有选择地调整默认样式差异。两者都不是页面性能优化的必需项,也不能解决 PHP 响应慢、接口数据过大或 JavaScript 长任务。
如果项目现有样式已经覆盖了相关差异,可以不额外引入;如果需要统一默认表现,可以选择适合项目的方案,避免叠加多个功能重叠的 reset。实际渲染开销仍要结合最终样式、页面结构和性能记录判断。
如何确认优化有效
每次围绕一个已定位的瓶颈修改,并在相同页面、数据和测试条件下比较结果。后端优化看接口与查询耗时,资源优化看传输体积和加载时间,前端优化看长任务、布局工作与操作响应。
加载提示、骨架屏或保留旧内容可以改善等待体验,但不会自动缩短请求时间。应同时观察内容何时可见、操作何时得到反馈,以及请求和渲染实际用了多久。
备注:内容仅供参考。
