【KO】前端面试七
117. 浏览器对队头阻塞有什么优化?【百度一面】
“队头阻塞”(Head-of-Line Blocking, HOLB)指因队列头部请求/数据包阻塞,导致后续请求/数据包无法处理的问题,常见于 HTTP/1.1 资源加载 和 TCP 传输 场景,浏览器及协议层的优化手段如下:
一、针对 HTTP/1.1 资源加载的队头阻塞优化
HTTP/1.1 因“同一 TCP 连接中,请求需串行发送”,若前一个请求阻塞(如大文件加载慢、网络波动),后续请求会被卡在队列头部。浏览器主要通过以下方式缓解:
-
同域名并发连接限制
- 浏览器为每个域名建立多个 TCP 连接(如 Chrome 默认为 6-8 个),将资源请求分散到不同连接中并行传输。例如,一个页面需加载 10 张图片,会分 2 批(每批 6 个)在不同连接中请求,避免单连接串行导致的阻塞。
- 注意:并发连接数并非越多越好,过多会增加服务器压力,因此浏览器会限制上限。
-
资源域名分片(Domain Sharding)
- 前端将静态资源(图片、CSS、JS)部署到多个子域名(如
img1.example.com
、img2.example.com
),每个子域名独立计算并发连接数。例如,用 3 个图片子域名,可获得3×6=18
个并发连接,大幅提升资源加载并行度,避免单域名连接数不足导致的队头阻塞。
- 前端将静态资源(图片、CSS、JS)部署到多个子域名(如
-
资源预加载与预连接
- 预连接(Preconnect):通过
<link rel="preconnect" href="https://cdn.example.com">
提前建立与目标域名的 TCP 连接(含 DNS 解析、TCP 握手、TLS 协商),后续请求直接复用连接,减少连接建立耗时,间接降低队头阻塞影响。 - 预加载(Preload):通过
<link rel="preload" href="critical.js" as="script">
优先加载关键资源(如首屏 JS、CSS),避免关键资源被非关键资源阻塞在队列中。
- 预连接(Preconnect):通过
二、针对 TCP 传输层的队头阻塞优化(浏览器依赖协议升级)
TCP 协议本身存在队头阻塞(若某数据包丢失,后续数据包需在接收端缓存,等待丢失包重传后才能处理),浏览器无法直接修改 TCP 协议,但可通过 升级 HTTP 版本 间接规避:
-
HTTP/2 多路复用(Multiplexing)
- HTTP/2 基于“二进制帧”传输,同一 TCP 连接中可同时发送多个请求/响应(每个请求对应一个独立的“流”),流之间相互独立,不会因某一个流的阻塞影响其他流。例如,同一连接中,图片请求阻塞时,JS 请求仍可正常传输,从根本上解决了 HTTP/1.1 的队头阻塞问题。
-
HTTP/3 基于 QUIC 协议
- HTTP/3 放弃 TCP,改用基于 UDP 的 QUIC 协议。QUIC 同样支持多路复用,且每个流的数据包丢失仅影响当前流(TCP 是整个连接阻塞),进一步降低了传输层队头阻塞的影响,同时还优化了连接建立速度(0-RTT 握手)。
三、浏览器渲染层的间接优化(减少资源加载阻塞)
- 异步加载 JS:通过
async
/defer
属性让 JS 加载不阻塞 HTML 解析和渲染,避免因 JS 加载慢导致的页面渲染阻塞,间接减少“资源加载队头阻塞”对用户体验的影响(详见 116 题)。 - CSS 优先级处理:浏览器会优先加载关键 CSS(首屏样式),非关键 CSS 延迟加载,避免因大体积 CSS 加载阻塞渲染,减少后续渲染任务的队头阻塞。
118. 如何实现一个图片懒加载(Lazy Loading)功能?【必会】
图片懒加载指“仅当图片进入或即将进入浏览器视口时,才加载图片资源”,核心目的是减少首屏请求数、节省带宽、提升页面加载速度。常见实现方案如下:
一、原生 loading="lazy"
属性(最简单,推荐优先使用)
现代浏览器(Chrome 77+、Firefox 75+、Safari 15.4+)原生支持图片懒加载,无需写 JS,只需给 <img>
标签添加 loading="lazy"
属性:
<!-- 原生懒加载:仅当图片进入视口时加载 -->
<img src="placeholder.jpg" <!-- 占位图(可选,如灰色背景图) -->data-src="real-image.jpg" <!-- 真实图片地址(若用自定义逻辑,需配合 JS) -->loading="lazy" alt="描述"<!-- 可选:设置预加载阈值,距离视口 200px 时开始加载 -->width="600" height="400" <!-- 必设:避免加载后页面重排 -->
>
- 优点:零 JS 代码、浏览器原生优化、兼容性覆盖主流现代浏览器。
- 缺点:不支持 IE 及老旧浏览器,需做降级处理;自定义控制能力弱(如无法自定义加载时机、加载动画)。
二、Intersection Observer API 实现(兼容性好,自定义能力强)
Intersection Observer
是浏览器提供的 API,可监听元素与视口(或指定容器)的“交叉状态”(即元素是否进入视口),无需手动计算滚动位置,性能优于传统 scroll
事件监听。
实现步骤:
- HTML 结构:用
data-src
存储真实图片地址,src
设为占位图; - JS 逻辑:创建
Intersection Observer
实例,监听所有懒加载图片,当图片进入视口时,替换src
为data-src
并停止监听。
<!-- HTML:占位图 + data-src 存真实地址 -->
<img class="lazy-img" src="placeholder.png" data-src="image1.jpg" alt="图1" width="600" height="400">
<img class="lazy-img" src="placeholder.png" data-src="image2.jpg" alt="图2" width="600" height="400"><script>
// JS:Intersection Observer 监听
document.addEventListener('DOMContentLoaded', () => {// 1. 选择所有懒加载图片const lazyImages = document.querySelectorAll('.lazy-img');// 2. 创建观察者实例:交叉时执行回调const observer = new IntersectionObserver((entries, observer) => {entries.forEach(entry => {// 3. 若图片进入视口(isIntersecting 为 true)if (entry.isIntersecting) {const img = entry.target;// 替换 src 为真实地址(加载图片)img.src = img.dataset.src;// 可选:加载完成后添加动画img.onload = () => {img.classList.add('fade-in');};// 4. 停止监听已加载的图片(避免重复触发)observer.unobserve(img);}});}, {// 可选配置:距离视口 200px 时开始加载(提前预加载)rootMargin: '200px 0px',// 可选:交叉比例达到 10% 时触发(默认 0,即只要有一点进入就触发)threshold: 0.1});// 5. 给所有懒加载图片添加监听lazyImages.forEach(img => {observer.observe(img);});
});
</script><style>
/* 可选:加载动画 */
.lazy-img {transition: opacity 0.3s ease