PAYATHON 2026

如何优化 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。实际渲染开销仍要结合最终样式、页面结构和性能记录判断。

如何确认优化有效

每次围绕一个已定位的瓶颈修改,并在相同页面、数据和测试条件下比较结果。后端优化看接口与查询耗时,资源优化看传输体积和加载时间,前端优化看长任务、布局工作与操作响应。

加载提示、骨架屏或保留旧内容可以改善等待体验,但不会自动缩短请求时间。应同时观察内容何时可见、操作何时得到反馈,以及请求和渲染实际用了多久。

备注:内容仅供参考。