HTML与Web 性能优化:构建高速响应的现代网站
HTML 与 Web 性能优化:构建高速响应的现代网站
引言
随着互联网用户对网站加载速度期望的不断提高,前端性能优化已经成为现代 Web 开发的核心竞争力。据 Google 研究表明,页面加载时间每增加 1 秒,用户跳出率就会增加 32%。用户期望页面在 2 秒内完成加载,超过 3 秒的加载时间将导致近 40% 的用户放弃等待。
HTML 作为网站的骨架,其结构和优化策略对页面性能有着决定性影响。一个精心设计的 HTML 结构不仅可以加快首屏渲染速度,还能在资源有限的情况下优先展示最重要的内容,为用户提供即时反馈。本文将深入探讨如何通过优化 HTML 结构来提升页面性能,减少用户等待时间,提高整体用户体验。
这篇文章将探讨相关话题,希望本文对你有帮助。
浏览器渲染机制:理解性能优化的基础
在进行任何性能优化之前,深入理解浏览器如何解析和渲染页面至关重要。只有掌握了这一基础知识,我们才能有针对性地进行优化。
关键渲染路径解析
浏览器将 HTML 转化为可视页面需要经过以下步骤,这一系列步骤被称为"关键渲染路径"(Critical Rendering Path):
-
解析 HTML 生成 DOM 树:浏览器逐行解析 HTML 标记,构建文档对象模型(DOM)。DOM 是页面结构的内存表示,包含所有 HTML 元素及其关系。
-
解析 CSS 生成 CSSOM 树:同时,浏览器解析外部 CSS 文件和样式元素,构建 CSS 对象模型(CSSOM)。这一过程会阻塞渲染,因为浏览器需要知道如何为元素设置样式。
-
合并 DOM 与 CSSOM 形成渲染树:DOM 和 CSSOM 合并成一个渲染树(Render Tree),其中只包含页面上可见的元素及其样式信息。隐藏的元素(如设置了
display: none
的元素)不会包含在渲染树中。 -
布局计算(Layout/Reflow):浏览器计算渲染树中每个元素的精确位置和大小,这一过程称为布局或回流。它决定了每个元素在视口中的准确位置。
-
绘制(Paint):布局完成后,浏览器将每个元素转换为屏幕上的实际像素,包括文本、颜色、边框、阴影等视觉属性。
-
合成(Composite):最后,浏览器将各个图层按照正确的顺序合成,形成最终的页面图像。
了解这一过程后,我们可以看到,任何影响这些步骤的因素都会影响页面的渲染速度。例如,外部 CSS 文件会阻塞渲染,因为浏览器必须等待 CSSOM 构建完成才能进行下一步。同样,脚本执行也可能阻塞渲染,特别是当脚本需要操作尚未加载的元素时。
以下是一个基本的 HTML 结构,我们将用它来理解影响渲染的因素:
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>页面标题</title><!-- 这里放置的CSS资源会阻塞渲染,因为浏览器需要等待CSSOM构建完成 --><link rel="stylesheet" href="styles.css"><!-- 默认情况下,脚本会阻塞HTML解析,因为它们可能会修改DOM --><script src="script.js"></script>
</head>
<body><header><!-- 首屏关键内容应该放在这里,确保最快显示给用户 --><h1>网站标题</h1><p>网站描述信息</p></header><!-- 非关键内容可以放在后面,或者使用延迟加载技术 --><main><article><h2>文章标题</h2><p>文章内容...</p></article></main><footer><!-- 页脚通常不是首屏关键内容 --><p>版权信息 © 2023</p></footer>
</body>
</html>
在上面的例子中,浏览器必须先下载并解析 styles.css
才能继续渲染过程,这就是为什么将关键 CSS 内联到 HTML 中可以加速首屏渲染 —— 它消除了额外的网络请求。同样,脚本默认会阻塞 HTML 解析,因为它们可能会修改 DOM 结构。
回流与重绘:性能瓶颈分析
回流(Reflow)和重绘(Repaint)是影响页面性能的两个关键因素,理解它们的区别和优化方法对提升页面性能至关重要。
回流(Reflow):当 DOM 元素的几何属性(如大小、位置、边距)发生变化时,浏览器需要重新计算元素的位置和尺寸,这个过程称为回流。回流是一个计算密集型操作,会大量消耗 CPU 资源,特别是在复杂布局中。
回流会触发整个渲染树的重新布局,其成本随着页面复杂度增加而显著增长。当一个元素发生回流时,它的所有子元素和祖先元素也可能需要重新布局,这就是为什么回流被认为是性能杀手。
以下操作会触发回流:
- 添加或删除可见的 DOM 元素
- 元素位置、尺寸、内容的改变
- 页面初始化渲染
- 浏览器窗口大小变化
- 获取某些属性(如
offsetWidth
、offsetHeight
、scrollTop
、clientWidth
等)
// 以下代码将触发多次回流,因为每一行都在改变元素的几何属性
const element = document.getElementById('example');
element.style.width = '300px'; // 触发回流
element.style.height = '200px'; // 再次触发回流
element.style.marginTop = '20px'; // 又一次触发回流
element.style.position = 'absolute'; // 再次触发回流// 以下代码也会触发回流,因为它们迫使浏览器计算最新的布局信息
console.log(element.offsetWidth); // 读取布局信息,触发回流
console.log(element.offsetHeight); // 再次触发回流
重绘(Repaint):当元素的外观(如颜色、背景、可见性等)发生变化,但不影响其布局时,浏览器会重新绘制元素,这个过程称为重绘。重绘的性能消耗比回流小,但在频繁触发时仍会影响性能。
以下操作会触发重绘但不会触发回流:
- 修改颜色、背景色、阴影等仅影响外观的属性
- 修改元素的可见性(如
visibility: hidden
,而非display: none
,后者会触发回流)
// 以下代码只会触发重绘,因为这些改变不影响元素的布局
element.style.color = 'red'; // 仅触发重绘
element.style.backgroundColor = 'blue'; // 仅触发重绘
element.style.boxShadow = '0 0 5px #000'; // 仅触发重绘
优化策略:为了最小化回流和重绘对性能的影响,我们可以采取以下策略:
- 批量修改 DOM:一次性修改多个样式,而不是逐个修改
// 不推荐:多次单独修改会触发多次回流或重绘
element.style.width = '300px';
element.style.height = '200px';
element.style.border = '1px solid black';// 推荐:合并修改,只触发一次回流
element.style.cssText = 'width: 300px; height: 200px; border: 1px solid black;';// 或使用 class 修改多个样式
element.classList.add('new-layout');
- 使用文档片段:处理多个 DOM 操作时,先在内存中进行修改,再一次性应用到 DOM 树
// 不推荐:直接在 DOM 中添加多个元素,每次添加都会触发回流
const list = document.getElementById('list');
for (let i = 0; i < 100; i++) {const item = document.createElement('li');item.textContent = `Item ${i}`;list.appendChild(item); // 每次循环都会触发回流
}// 推荐:使用文档片段,只触发一次回流
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {const item = document.createElement('li');item.textContent = `Item ${i}`;fragment.appendChild(item); // 在内存中操作,不触发回流
}
list.appendChild(fragment); // 只触发一次回流
- 避免强制同步布局:避免在修改 DOM 后立即读取布局信息
// 不推荐:强制同步布局
for (let i = 0; i < elements.length; i++) {elements[i].style.width = (elements[i].offsetWidth + 10) + 'px'; // 读取后再修改,每次循环都触发回流
}// 推荐:先读取后修改
const widths = [];
for (let i = 0; i < elements.length; i++) {widths[i] = elements[i].offsetWidth; // 先一次性读取所有宽度
}
for (let i = 0; i < elements.length; i++) {elements[i].style.width = (widths[i] + 10) + 'px'; // 然后一次性修改
}
- 使用 CSS 硬件加速:利用 GPU 加速渲染,减轻 CPU 负担
/* 启用 GPU 加速的 CSS 属性 */
.accelerated {transform: translateZ(0); /* 或 translate3d(0,0,0) */will-change: transform; /* 提示浏览器该元素将来可能发生变化 */
}
通过理解回流和重绘的机制,并采取适当的优化策略,我们可以显著减少这些操作对页面性能的负面影响,提供更流畅的用户体验。
HTML 结构优化策略
HTML 结构的组织方式直接影响页面的加载和渲染速度。通过优化 HTML 结构,我们可以在不牺牲内容的情况下,显著提升页面的首屏加载速度。
关键资源优先加载
在 Web 性能优化中,"关键资源"指的是那些必须在首屏渲染前加载的资源,它们直接影响用户的初始体验。识别并优先加载这些资源是提升首屏性能的关键。
内联关键 CSS
传统方式下,浏览器需要先下载外部 CSS 文件才能渲染页面,这会导致首屏渲染延迟。通过内联首屏关键 CSS,我们可以消除这一网络请求,使浏览器能够立即开始渲染页面。
"关键 CSS"是指渲染首屏内容所必需的最小 CSS 集合。通过工具分析(如 Critical CSS 或 PageSpeed Insights)或手动提取,我们可以确定页面首屏渲染所需的关键样式。
<head><!-- 内联关键 CSS,直接在 HTML 中提供首屏所需样式 --><style>/* 仅包含首屏渲染所需的关键样式 */body { margin: 0; font-family: 'Arial', sans-serif; }header { background-color: #f8f9fa; padding: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);}.hero-title { font-size: 2rem; font-weight: bold; color: #333;margin-bottom: 1rem;}.hero-text {font-size: 1.1rem;color: #555;max-width: 600px;margin: 0 auto;}/* 其他首屏关键样式 */</style><!-- 其余非关键样式可以异步加载,不阻塞渲染 --><link rel="preload" href="non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'"><noscript><link rel="stylesheet" href="non-critical.css"></noscript>
</head>
上面的代码展示了内联关键 CSS 的实现方式。我们将首屏渲染所需的样式直接嵌入到 HTML 中,而将其余样式以异步方式加载。这种方法的好处是:
- 消除了阻塞渲染的外部 CSS 请求
- 允许浏览器立即开始渲染首屏内容
- 用户可以更快地看到有样式的内容,而不是白屏或无样式内容
需要注意的是,内联 CSS 不会被浏览器缓存,因此对于返回访问者来说可能不如外部 CSS 文件高效。这就是为什么我们只内联关键 CSS,而将其余样式放在外部文件中。
预加载与预连接策略
现代浏览器提供了多种资源提示机制,允许开发者指导浏览器如何优先加载关键资源。这些机制包括预解析(dns-prefetch)、预连接(preconnect)、预加载(preload)和预获取(prefetch)。
DNS 预解析(dns-prefetch):
DNS 解析是浏览器请求资源前必须完成的步骤,它将域名转换为 IP 地址。这个过程可能需要几十到几百毫秒。通过 DNS 预解析,浏览器可以在空闲时提前完成这一步骤,为后续资源请求节省时间。
<!-- DNS 预解析:告诉浏览器提前解析域名 -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
这对于页面需要从多个第三方域名加载资源的情况特别有用,如字体、图片、分析脚本等。预解析可以减少用户等待时间,因为当浏览器真正需要请求这些资源时,DNS 解析已经完成。
预连接(preconnect):
预连接更进一步,它不仅完成 DNS 解析,还建立 TCP 连接,并在需要时完成 TLS 握手。这样,当浏览器需要从该域名请求资源时,可以立即开始传输数据,无需等待连接建立。
<!-- 预连接:完成 DNS 解析、TCP 握手和 TLS 协商 -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
预连接适用于你确定页面很快就会从该域名请求资源的情况。但不要滥用预连接,因为每个打开的连接都会消耗系统资源,过多的预连接反而会降低性能。
预加载(preload):
预加载允许指定当前页面必需的关键资源,提示浏览器应尽快下载这些资源。与其他资源提示不同,预加载是强制性的,浏览器必须执行预加载请求。
<!-- 预加载关键字体 -->
<link rel="preload" href="critical-font.woff2" as="font" type="font/woff2" crossorigin><!-- 预加载首屏大图 -->
<link rel="preload" href="hero-image.jpg" as="image"><!-- 预加载关键 JavaScript -->
<link rel="preload" href="critical-component.js" as="script">
预加载的优势在于它可以改变资源的加载优先级,确保最重要的资源最先加载。例如,在一个内容丰富的新闻网站中,预加载主文章的配图可以显著改善用户体验,因为这些图片通常是用户关注的焦点。
注意,预加载需要指定正确的 as
属性,以便浏览器设置正确的请求标头和优先级。而且,预加载的资源必须在页面中实际使用,否则会浪费用户的带宽。
预获取(prefetch):
预获取与预加载不同,它是为未来的导航或用户操作预先获取资源,而非当前页面所需。预获取的优先级较低,浏览器会在空闲时下载这些资源。
<!-- 预获取下一页可能需要的资源 -->
<link rel="prefetch" href="next-page.js">
<link rel="prefetch" href="article-content.json">
预获取适用于你能预测用户下一步操作的情况。例如,在分页列表中,预获取下一页的数据;或在电子商务网站中,预获取用户可能点击的产品详情页面资源。
各种预加载指令的对比与应用场景:
技术 | 用途 | 适用场景 | 具体实现方式 |
---|---|---|---|
dns-prefetch | 提前解析域名 | 第三方资源较多 | <link rel="dns-prefetch" href="https://example.com"> |
preconnect | 提前建立连接 | 需要尽快从特定域名加载资源 | <link rel="preconnect" href="https://example.com"> |
preload | 当前页面必需资源 | 关键字体、图片、CSS、JS | <link rel="preload" href="font.woff2" as="font"> |
prefetch | 未来页面可能需要 | 下一页内容、可能点击的路径 | <link rel="prefetch" href="next-page.html"> |
在实际应用中,这些技术通常结合使用。例如,对于重要的第三方资源,可以先使用 dns-prefetch 提前解析域名,然后使用 preconnect 建立连接,最后使用 preload 加载具体资源。这种组合策略可以最大限度地减少资源加载延迟。
以字体加载为例,合理使用这些技术可以显著减少字体闪烁问题:
<!-- 对字体服务进行 DNS 预解析和预连接 -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin><!-- 预加载关键字体文件 -->
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" as="style">
<link rel="preload" href="https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu4mxK.woff2" as="font" type="font/woff2" crossorigin><!-- 实际加载字体样式 -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
延迟加载非关键资源
并非页面上的所有资源都对首屏渲染至关重要。通过延迟加载非关键资源,我们可以减少初始页面负载,加快首屏渲染速度,提供更好的用户体验。
脚本加载优化
JavaScript 是现代网站不可或缺的组成部分,但它也是影响页面加载性能的主要因素之一。默认情况下,当浏览器遇到 <script>
标签时,它会停止 HTML 解析,下载并执行脚本,然后才继续解析。这会严重延迟页面渲染。
HTML5 引入了 async
和 defer
属性,允许开发者控制脚本的加载和执行时机:
普通脚本:阻塞 HTML 解析,立即下载并执行
<!-- 立即加载并执行,阻塞 HTML 解析和渲染 -->
<script src="critical.js"></script>
这种方式适用于页面渲染前必须执行的脚本,如关键功能或初始状态设置。但应尽量减少这类脚本的使用,因为它们会延迟整个页面的渲染。
异步脚本(async):不阻塞 HTML 解析,下载完成后立即执行
<!-- 异步加载,加载完成后立即执行,不阻塞 HTML 解析但可能阻塞渲染 -->
<script src="analytics.js" async></script>
async
脚本在下载时不会阻塞 HTML 解析,但会在下载完成后立即执行,可能会中断 HTML 解析。此外,async
脚本的执行顺序不确定,取决于下载完成的时间。这种方式适用于不依赖于页面其他部分且不被其他脚本依赖的独立脚本,如分析脚本、广告代码等。
延迟脚本(defer):不阻塞 HTML 解析,DOM 解析完成后按顺序执行
<!-- 延迟加载,DOM 解析完成后按顺序执行,不阻塞 HTML 解析和渲染 -->
<script src="non-critical.js" defer></script>
defer
脚本在 HTML 解析时并行下载,但会等到 HTML 解析完成后、DOMContentLoaded 事件触发前按照它们在文档中出现的顺序执行。这种方式适用于需要操作 DOM 但不影响首屏渲染的脚本,如页面交互功能、评论系统等。
在实际应用中,我们可以根据脚本的特性和重要性选择合适的加载方式:
<head><!-- 关键脚本:立即加载和执行 --><script src="critical-feature.js"></script><!-- 分析脚本:异步加载,不阻塞渲染 --><script src="analytics.js" async></script><!-- 页面交互脚本:延迟加载,DOM 解析完成后执行 --><script src="ui-components.js" defer></script><script src="comments.js" defer></script><!-- 或者使用动态脚本加载,完全控制加载时机 --><script>// 页面加载完成后再加载非关键脚本window.addEventListener('load', function() {const script = document.createElement('script');script.src = 'non-critical-feature.js';document.body.appendChild(script);});</script>
</head>
通过这种方式,我们可以确保关键功能快速可用,同时不让非关键脚本拖慢页面加载。
脚本加载优化是一个平衡性能和功能的过程。对于每个脚本,我们都应该问自己:这个脚本对首屏渲染是否必要?它是否依赖于 DOM?其他脚本是否依赖它?基于这些问题,选择最适合的加载策略。
图片懒加载实现
图片通常是网页中最大的资源之一,占据了大量带宽。对于长页面或内容丰富的网站,一次性加载所有图片会严重影响初始加载性能。图片懒加载是一种只在需要时(通常是图片滚动到视口附近)才加载图片的技术,可以显著减少初始页面负载。
原生懒加载属性:
现代浏览器提供了原生的懒加载支持,通过简单的 loading="lazy"
属性即可实现:
<!-- 原生懒加载属性 -->
<img src="image.jpg" loading="lazy" alt="描述"><!-- 或结合占位符实现更平滑的体验 -->
<img src="placeholder.jpg" data-src="actual-image.jpg" loading="lazy" alt="描述">
原生懒加载的优势在于简单易用,无需 JavaScript 支持,浏览器能够智能地决定何时加载图片。然而,它也有一些限制,如浏览器兼容性问题(较旧的浏览器不支持)以及无法精细控制加载时机。
使用 Intersection Observer API:
对于需要更精细控制或更广泛浏览器支持的场景,可以使用 Intersection Observer API 实现懒加载:
// 使用 Intersection Observer 实现懒加载
document.addEventListener("DOMContentLoaded", function() {// 选择所有带有 data-src 属性的图片const lazyImages = document.querySelectorAll("img[data-src]");// 创建观察者实例const imageObserver = new IntersectionObserver((entries, observer) => {entries.forEach(entry => {// 当图片进入视口if (entry.isIntersecting) {const img = entry.target;// 将 data-src 的值赋给 src 属性,触发图片加载img.src = img.dataset.src;// 加载后移除 data-src 属性img.removeAttribute("data-src");// 图片加载后停止观察imageObserver.unobserve(img);}});}, {// 配置根元素和阈值rootMargin: "0px 0px 200px 0px", // 提前 200px 加载图片,创造更平滑的体验threshold: 0.01 // 当图片有 1% 进入视口时触发加载});// 对每个懒加载图片启动观察lazyImages.forEach(img => imageObserver.observe(img));
});
Intersection Observer API 是一个强大的工具,它允许异步观察元素与其祖先元素或视口的交叉状态变化。与传统的滚动事件监听相比,它更高效,因为它不在主线程上运行,不会导致性能问题。此外,通过调整 rootMargin
参数,我们可以控制图片的预加载距离,创造更平滑的用户体验。
传统滚动事件监听方法:
对于需要支持更旧浏览器的场景,可以使用传统的滚动事件监听实现懒加载:
// 传统滚动事件监听实现懒加载
function lazyLoad() {const lazyImages = document.querySelectorAll('img[data-src]');const windowHeight = window.innerHeight;lazyImages.forEach(img => {const rect = img.getBoundingClientRect();// 当图片接近视口时加载if (rect.top <= windowHeight + 200) {img.src = img.dataset.src;img.removeAttribute("data-src");}});
}// 节流函数,限制滚动事件处理频率
function throttle(func, limit) {let lastFunc;let lastRan;return function() {const context = this;const args = arguments;if (!lastRan) {func.apply(context, args);lastRan = Date.now();} else {clearTimeout(lastFunc);lastFunc = setTimeout(function() {if ((Date.now() - lastRan) >= limit) {func.apply(context, args);lastRan = Date.now();}}, limit - (Date.now() - lastRan));}}
}// 添加滚动事件监听
document.addEventListener('scroll', throttle(lazyLoad, 200));
// 初始调用一次,加载初始视口中的图片
document.addEventListener('DOMContentLoaded', lazyLoad);
这种方法通过监听滚动事件并检查图片位置来实现懒加载。为了避免滚动事件的频繁触发导致性能问题,我们使用节流函数限制事件处理的频率。
不同懒加载实现方法的效果对比:
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
原生属性 loading=“lazy” | 简单实现,浏览器原生支持,无需 JavaScript | 浏览器兼容性有限,控制粒度较低 | 简单项目,现代浏览器用户群 |
Intersection Observer | 性能好,不阻塞主线程,配置灵活 | 需要 JavaScript 支持,老浏览器需要 polyfill | 大多数现代网站 |
传统滚动事件监听 | 兼容性好,几乎所有浏览器支持 | 频繁触发,性能较差,需要手动优化 | 需要支持旧浏览器的项目 |
在实际项目中,图片懒加载的效果非常显著。
除了常规图片,懒加载也适用于其他资源,如视频、iframe 和大型 JavaScript 库。例如,对于嵌入的视频播放器,可以先显示一个缩略图,当用户滚动到位置时再加载实际播放器:
<div class="video-container" data-video-id="VIDEO_ID"><img src="video-thumbnail.jpg" alt="视频预览" class="video-thumbnail"><button class="play-button">播放视频</button>
</div><script>document.querySelectorAll('.play-button').forEach(button => {button.addEventListener('click', function() {const container = this.parentElement;const videoId = container.dataset.videoId;// 替换缩略图为实际视频播放器container.innerHTML = `<iframe src="https://www.youtube.com/embed/${videoId}?autoplay=1" frameborder="0" allowfullscreen></iframe>`;});});
</script>
图片懒加载是一种简单但非常有效的性能优化策略,它不仅可以提升页面加载速度,还可以节省带宽和减少不必要的资源消耗。根据项目需求和浏览器支持情况,选择合适的实现方法,可以在不影响用户体验的前提下显著提高性能。
内容优先级渲染与骨架屏
在页面加载过程中,用户体验很大程度上取决于内容呈现的速度和方式。通过优先渲染关键内容并使用骨架屏技术,我们可以显著改善用户体验和感知性能。
内容优先级策略
用户访问网页时最关心的通常是首屏内容。通过合理组织 HTML 结构,我们可以确保最重要的内容优先渲染:
<body><!-- 1. 首屏关键内容优先(最先加载和渲染) --><header><h1>网站标题</h1><nav><a href="/">首页</a><a href="/products">产品</a><a href="/about">关于</a></nav></header><main><!-- 2. 主要内容区域 --><article><h2>文章标题</h2><p>重要的首段内容...</p><!-- 3. 视口外或次要内容可以延迟加载 --><div class="deferred-content"><p>详细段落...</p><img data-src="article-image.jpg" loading="lazy" alt="文章配图"></div></article><!-- 4. 相关内容,通常在首屏之外 --><aside><h3>相关文章</h3><!-- 使用延迟加载 --></aside></main><!-- 5. 页脚内容放在最后(通常不在首屏) --><footer><p>版权信息 © 2023</p><div class="footer-links"><!-- 延迟加载的链接和图标 --></div></footer><!-- 6. 非关键脚本放在页面底部 --><script src="non-critical.js" defer></script>
</body>
这种结构确保了浏览器首先解析和渲染用户最关心的内容。HTML 是流式解析的,浏览器会按照文档顺序处理内容,因此将关键内容放在前面可以加快首屏渲染。
对于复杂的内容,我们可以使用客户端渲染与服务器端渲染相结合的方式,在服务器上渲染首屏关键内容,而将次要内容留给客户端处理:
<!-- 服务器端渲染的关键内容 -->
<div id="critical-content"><!-- 由服务器直接输出的 HTML --><h1>产品标题</h1><p>产品描述</p><div class="price">¥299.00</div>
</div><!-- 客户端渲染的非关键内容 -->
<div id="non-critical-content"><!-- 将由 JavaScript 填充 -->
</div><script defer>// 页面加载后获取并渲染非关键内容window.addEventListener('load', function() {fetch('/api/product/details').then(response => response.json()).then(data => {document.getElementById('non-critical-content').innerHTML = `<div class="reviews"><h3>用户评价 (${data.reviews.length})</h3><ul>${data.reviews.map(review => `<li>${review.text}</li>`).join('')}</ul></div><div class="related-products"><h3>相关商品</h3><!-- 相关产品内容 --></div>`;});});
</script>
采用内容优先级策略的好处包括:
- 更快的首屏渲染时间:用户几乎立即能看到核心内容
- 更好的用户体验:提供即时反馈,减少用户等待感知
- 更高的用户留存率:研究表明,用户更愿意停留在快速加载的网站上
骨架屏技术实现
骨架屏是一种在内容加载过程中显示的低保真界面预览,它模拟了实际内容的布局结构,给用户一种页面已部分加载的印象,减少等待焦虑。
基础骨架屏实现:
<style>/* 骨架屏基础样式 */.skeleton-container {padding: 15px;}.skeleton-line {height: 15px;margin-bottom: 10px;border-radius: 4px;background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);background-size: 200% 100%;animation: shimmer 1.5s infinite;}.skeleton-line.short { width: 40%; }.skeleton-line.medium { width: 70%; }.skeleton-line.long { width: 100%; }.skeleton-image {width: 100%;height: 200px;border-radius: 8px;margin: 15px 0;background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);background-size: 200% 100%;animation: shimmer 1.5s infinite;}@keyframes shimmer {0% { background-position: -200% 0; }100% { background-position: 200% 0; }}/* 实际内容初始隐藏 */.content-container {opacity: 0;transition: opacity 0.3s ease;}.content-container.loaded {opacity: 1;}
</style><main><!-- 骨架屏 - 初始显示 --><div class="skeleton-container" id="content-skeleton"><div class="skeleton-line short"></div><div class="skeleton-line medium"></div><div class="skeleton-line long"></div><div class="skeleton-image"></div><div class="skeleton-line long"></div><div class="skeleton-line medium"></div><div class="skeleton-line long"></div></div><!-- 实际内容 - 初始隐藏 --><div class="content-container" id="actual-content" style="display: none;"><h2>文章标题</h2><p class="summary">文章摘要内容...</p><img src="article-image.jpg" alt="文章配图"><div class="article-body"><p>详细内容段落...</p><!-- 更多内容 --></div></main><script>// 模拟内容加载过程window.addEventListener('load', function() {// 模拟数据加载延迟setTimeout(() => {// 隐藏骨架屏document.getElementById('content-skeleton').style.display = 'none';// 显示实际内容const actualContent = document.getElementById('actual-content');actualContent.style.display = 'block';// 添加短暂延迟后添加 loaded 类,实现平滑过渡setTimeout(() => {actualContent.classList.add('loaded');}, 50);}, 1000); // 根据实际内容加载时间调整});</script>
组件化骨架屏:
在实际项目中,我们通常会创建可复用的骨架屏组件,特别是在使用前端框架如 React、Vue 或 Angular 时:
// React 骨架屏组件示例
function SkeletonCard() {return (<div className="skeleton-card"><div className="skeleton-image"></div><div className="skeleton-title"></div><div className="skeleton-text"></div><div className="skeleton-text short"></div></div>);
}// 使用方式
function ProductList({ isLoading, products }) {return (<div className="product-list">{isLoading ? (// 显示骨架屏Array(6).fill().map((_, index) => (<SkeletonCard key={`skeleton-${index}`} />))) : (// 显示实际内容products.map(product => (<ProductCard key={product.id} product={product} />)))}</div>);
}
自动生成骨架屏:
对于复杂应用,手动创建骨架屏可能很繁琐。我们可以使用工具自动生成骨架屏,如 page-skeleton-webpack-plugin 或 react-content-loader:
// 使用 react-content-loader 创建自定义骨架屏
import ContentLoader from 'react-content-loader'const ProductSkeleton = () => (<ContentLoaderspeed={2}width={400}height={160}viewBox="0 0 400 160"backgroundColor="#f3f3f3"foregroundColor="#ecebeb"><rect x="0" y="0" rx="3" ry="3" width="70" height="70" /><rect x="80" y="17" rx="3" ry="3" width="250" height="13" /><rect x="80" y="40" rx="3" ry="3" width="150" height="13" /><rect x="0" y="80" rx="3" ry="3" width="350" height="10" /><rect x="0" y="100" rx="3" ry="3" width="400" height="10" /><rect x="0" y="120" rx="3" ry="3" width="360" height="10" /></ContentLoader>
)
骨架屏的好处不仅在于减少用户感知的加载时间,还在于提供了更流畅的体验过渡。研究表明,当用户看到进度指示或内容结构预览时,他们更能容忍实际加载时间,感知性能比实际性能更重要。
高级优化策略
随着 Web 技术的发展,浏览器提供了越来越多的高级优化功能,利用这些特性可以进一步提升页面性能。
资源提示与预加载优化
前面我们已经介绍了基本的资源提示,如 preload
和 prefetch
。这里我们将深入探讨更多高级应用场景和组合策略。
HTTP/2 服务器推送与资源提示结合
HTTP/2 服务器推送允许服务器在客户端请求 HTML 时主动推送关键资源,无需等待浏览器解析 HTML 后再发起请求。结合资源提示,我们可以创建更高效的加载策略:
<!-- 在 HTTP 头部使用 Link 指示服务器推送 -->
<!-- 服务器配置示例 (Apache): -->
<!--
<IfModule mod_headers.c><FilesMatch "index.html">Header add Link "</css/critical.css>; rel=preload; as=style"Header add Link "</js/critical.js>; rel=preload; as=script"</FilesMatch>
</IfModule>
--><!-- 同时在 HTML 中也加入资源提示,作为降级方案 -->
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/js/critical.js" as="script">
服务器推送最适合用于推送关键 CSS 和 JavaScript,它们必须尽早加载以减少渲染阻塞时间。然而,过度使用服务器推送可能会浪费带宽,因为浏览器缓存中已有的资源也会被推送。
为避免这个问题,可以使用 Cookie 记录客户端已缓存的资源:
// 客户端记录已缓存资源
document.addEventListener('DOMContentLoaded', () => {const cached = localStorage.getItem('cached-resources') || '';const resources = cached.split(',').filter(Boolean);// 添加新缓存的资源if (!resources.includes('critical.css')) {resources.push('critical.css');}localStorage.setItem('cached-resources', resources.join(','));// 设置 cookie 告知服务器document.cookie = `cached-resources=${resources.join(',')}; path=/; max-age=86400`;
});
<?php
// 服务器端检查缓存状态决定是否推送
$cachedResources = isset($_COOKIE['cached-resources']) ? explode(',', $_COOKIE['cached-resources']) : [];if (!in_array('critical.css', $cachedResources)) {header('Link: </css/critical.css>; rel=preload; as=style', false);
}
优先级提示
较新的浏览器支持 fetchpriority
属性,它允许开发者明确指定资源的加载优先级:
<!-- 高优先级加载首屏关键图片 -->
<img src="hero.jpg" fetchpriority="high" alt="主视觉图"><!-- 低优先级加载非首屏图片 -->
<img src="background.jpg" fetchpriority="low" alt="背景图"><!-- 对脚本也可以使用 -->
<script src="critical.js" fetchpriority="high"></script>
<script src="analytics.js" fetchpriority="low"></script>
fetchpriority
属性与浏览器的默认优先级机制协同工作,为开发者提供了更细粒度的控制。例如,默认情况下,在视口内的图片优先级高于视口外的图片,但对于首屏大型横幅图片,我们可能希望即使它暂时在视口外,也能获得高优先级加载。
按需加载与模块预加载
现代 JavaScript 支持 ES 模块和动态导入,这允许我们实现更精细的代码分割和按需加载:
// 基本的动态导入
const loadFeature = async () => {const { default: feature } = await import('./features/non-critical-feature.js');feature.init();
};// 当用户与特定元素交互时加载
document.querySelector('.feature-button').addEventListener('click', loadFeature);// 结合 IntersectionObserver 实现预加载
const preloadFeatureModule = () => {const observer = new IntersectionObserver((entries, observer) => {entries.forEach(entry => {if (entry.isIntersecting) {// 用户接近相关区域时预加载模块import('./features/non-critical-feature.js');observer.disconnect();}});},{ rootMargin: '200px 0px' });observer.observe(document.querySelector('.feature-section'));
};// 页面加载后设置模块预加载观察
window.addEventListener('load', preloadFeatureModule);
这种方法允许我们只加载用户实际需要的代码,同时又能通过预加载减少实际使用时的等待时间。结合 Webpack、Rollup 或 Vite 等构建工具的代码分割功能,我们可以实现更细粒度的资源控制。
使用 Content-Visibility 优化渲染性能
CSS content-visibility
属性是一个强大的性能优化工具,它允许浏览器跳过屏幕外内容的渲染,显著提升长页面的性能。
基本用法
<style>.defer-visible {content-visibility: auto;contain-intrinsic-size: 0 500px; /* 预估高度,避免滚动条跳动 */}
</style><section class="product-list defer-visible"><!-- 大量产品列表,初始不在视口时将跳过渲染 --><div class="product">产品1详情...</div><div class="product">产品2详情...</div><!-- 更多产品... -->
</section>
content-visibility: auto
告诉浏览器,当元素不在视口附近时,可以跳过其内容的渲染。这与懒加载不同,懒加载是推迟资源的加载,而 content-visibility
是推迟内容的渲染,即使内容已加载。
contain-intrinsic-size
属性提供元素的预估大小,这样浏览器在实际渲染内容前就可以正确计算布局,避免滚动条跳动。预估值应尽可能接近实际渲染后的大小,但不需要完全精确。
实际应用场景
content-visibility
最适合用于:
- 长列表页面:产品列表、文章列表、评论系统等
- 复杂组件:包含大量 DOM 节点的复杂 UI 组件
- 折叠面板:默认隐藏的详情面板
<style>.comment-section {content-visibility: auto;contain-intrinsic-size: 0 2000px; /* 预估评论区总高度 */}.product-details {content-visibility: auto;contain-intrinsic-size: 0 800px;}.footer-content {content-visibility: auto;contain-intrinsic-size: 0 400px;}
</style><article><h1>产品名称</h1><div class="product-summary"><!-- 首屏关键信息,不使用 content-visibility --><img src="product.jpg" alt="产品图片"><p class="price">¥299.00</p><button>加入购物车</button></div><div class="product-details"><!-- 详细信息,使用 content-visibility 优化 --><h2>产品详情</h2><p>详细描述...</p><table><!-- 规格参数表 --></table></div><div class="comment-section"><!-- 评论区,使用 content-visibility 优化 --><h2>用户评价 (123)</h2><!-- 大量评论内容 --></div>
</article><footer><div class="footer-content"><!-- 页脚内容,使用 content-visibility 优化 --></div>
</footer>
在一个包含上千个 DOM 节点的复杂页面上,应用 content-visibility
后,初始渲染时间可能会从几秒减少到几百毫秒。Chrome 团队的测试表明,对于具有大量离屏内容的页面,渲染性能可以提高 40% 到 70%。
Content-Visibility 与传统优化的对比
特性 | Content-Visibility | 传统懒加载 | 虚拟滚动 |
---|---|---|---|
实现复杂度 | 低(纯 CSS) | 中(需要 JS) | 高(需要特殊库) |
性能改善 | 仅渲染可见内容 | 仅加载可见资源 | 仅保留可见 DOM |
内存使用 | 中等(DOM 存在但不渲染) | 随着滚动增加 | 低(仅保留可见 DOM) |
适用场景 | 长页面,复杂组件 | 图片列表,多媒体内容 | 极大数据集,无限滚动 |
浏览器支持 | 有限(主要是 Chrome) | 广泛 | 依赖 JavaScript 实现 |
对于不支持 content-visibility
的浏览器,我们可以使用特性检测提供优雅降级:
// 检测是否支持 content-visibility
if ('content-visibility' in document.body.style) {// 浏览器支持,添加相关类document.querySelectorAll('.defer-render').forEach(el => {el.classList.add('use-content-visibility');});
} else {// 不支持,使用备选方案,如懒加载或虚拟滚动setupAlternativeOptimization();
}
/* 仅在支持时应用 content-visibility */
.use-content-visibility {content-visibility: auto;contain-intrinsic-size: 0 500px;
}
HTTP 优化与缓存策略
合理的 HTTP 缓存策略可以显著减少网络请求,提高页面加载速度,特别是对于重复访问的用户。
HTTP 缓存控制
通过设置适当的 HTTP 缓存头,我们可以指导浏览器如何缓存不同类型的资源:
<!-- 服务器端配置示例(Apache .htaccess) -->
<!--
# HTML 文件 - 每次验证是否有更新
<FilesMatch "\.(html|htm)$">Header set Cache-Control "max-age=0, must-revalidate"
</FilesMatch># CSS, JS, 图片等静态资源 - 长期缓存
<FilesMatch "\.(css|js|jpg|jpeg|png|gif|ico|webp|svg|woff2)$">Header set Cache-Control "max-age=31536000, immutable"
</FilesMatch># 带有版本号的文件 - 永久缓存
<FilesMatch "\.([0-9a-f]{8,})\.">Header set Cache-Control "max-age=31536000, immutable"
</FilesMatch>
-->
对于频繁更新的 HTML 文件,我们设置了 max-age=0, must-revalidate
,这会使浏览器每次都检查文件是否有更新。而对于静态资源,我们设置了 max-age=31536000, immutable
(一年的缓存时间),这些资源会长期保存在浏览器缓存中。
immutable
指令告诉浏览器资源内容不会改变,即使用户刷新页面也不需要重新验证缓存,这可以避免不必要的条件请求。
内容哈希与版本控制
为了最大化缓存效益,同时确保用户能获取最新内容,我们应该在文件名中包含内容哈希:
<!-- 构建过程会自动生成包含哈希的文件名 -->
<link rel="stylesheet" href="styles.a8f3d2c7.css">
<script src="main.b7e92f1d.js"></script>
通过在文件名中包含内容哈希,每当文件内容变化时,文件名也会随之变化。这样,浏览器会将新文件视为全新资源,而不会使用缓存。构建工具如 Webpack、Rollup 和 Parcel 都支持自动生成哈希文件名。
服务器端缓存控制
除了客户端缓存,我们还应考虑服务器端缓存:
// Node.js Express 服务器缓存控制示例
const express = require('express');
const app = express();// 静态资源服务,设置长期缓存
app.use('/static', express.static('public', {maxAge: '1y',immutable: true,etag: true,lastModified: true
}));// HTML 和 API 响应的简单 ETag 实现
app.get('/', (req, res) => {const html = generateHTML(); // 生成页面内容const etag = generateETag(html); // 根据内容生成 ETag// 检查客户端缓存是否仍然有效if (req.headers['if-none-match'] === etag) {res.status(304).end(); // 内容未修改,返回 304} else {res.setHeader('ETag', etag);res.setHeader('Cache-Control', 'no-cache'); // 每次验证是否有更新res.send(html);}
});
对于 API 响应,我们可以使用 ETag 和条件请求实现高效缓存:
// API 响应缓存控制示例
app.get('/api/products', (req, res) => {const data = getProducts(); // 获取产品数据const etag = generateETag(data);if (req.headers['if-none-match'] === etag) {res.status(304).end(); // 数据未变化,返回 304} else {res.setHeader('ETag', etag);res.setHeader('Cache-Control', 'private, max-age=300'); // 私有缓存,5分钟res.json(data);}
});
Service Worker 缓存策略
Service Worker 提供了更强大的缓存控制能力,允许我们实现离线访问和自定义缓存策略:
// service-worker.js
self.addEventListener('install', event => {event.waitUntil(caches.open('static-v1').then(cache => {return cache.addAll(['/','/styles.css','/app.js','/logo.png']);}));
});self.addEventListener('fetch', event => {event.respondWith(caches.match(event.request).then(response => {// 缓存优先策略:先查找缓存,如果有缓存则使用缓存if (response) {return response;}// 没有缓存,则请求网络return fetch(event.request).then(networkResponse => {// 检查是否是需要缓存的资源if (event.request.url.includes('/static/') && event.request.method === 'GET') {// 克隆响应,因为响应流只能使用一次const responseToCache = networkResponse.clone();caches.open('dynamic-v1').then(cache => {cache.put(event.request, responseToCache);});}return networkResponse;});}));
});// 清理旧版本缓存
self.addEventListener('activate', event => {const cacheWhitelist = ['static-v1', 'dynamic-v1'];event.waitUntil(caches.keys().then(cacheNames => {return Promise.all(cacheNames.map(cacheName => {if (cacheWhitelist.indexOf(cacheName) === -1) {return caches.delete(cacheName);}}));}));
});
Service Worker 可以实现多种缓存策略:
- 缓存优先(Cache First):先检查缓存,缓存未命中时才请求网络
- 网络优先(Network First):先尝试网络请求,失败时才使用缓存
- 仅缓存(Cache Only):只使用缓存,适用于离线访问的静态资源
- 仅网络(Network Only):只使用网络,不缓存,适用于实时数据
- 同步缓存和网络(Stale-While-Revalidate):先返回缓存,同时更新缓存,下次返回更新后的内容
// 同步缓存和网络策略示例
self.addEventListener('fetch', event => {if (event.request.url.includes('/api/')) {event.respondWith(caches.open('api-cache').then(cache => {return cache.match(event.request).then(cachedResponse => {const fetchPromise = fetch(event.request).then(networkResponse => {cache.put(event.request, networkResponse.clone());return networkResponse;});// 如果有缓存,立即返回缓存,同时更新缓存// 如果没有缓存,等待网络响应return cachedResponse || fetchPromise;});}));}
});
通过实施合理的 HTTP 缓存策略和 Service Worker,我们可以显著减少网络请求,提高页面加载速度,甚至实现离线访问。这对于移动用户和网络条件不稳定的用户尤为重要。
性能测量与监控
优化是一个持续的过程,需要基于实际数据进行决策。建立有效的性能测量和监控系统,是确保优化工作有针对性的关键。
核心 Web 指标分析
Google 定义的核心 Web 指标(Core Web Vitals)是衡量网页用户体验的重要指标,包括加载性能、交互性和视觉稳定性:
- 最大内容绘制(LCP, Largest Contentful Paint):衡量加载性能,表示页面主要内容加载完成的时间点
- 首次输入延迟(FID, First Input Delay):衡量交互性,表示用户首次与页面交互时的响应延迟
- 累积布局偏移(CLS, Cumulative Layout Shift):衡量视觉稳定性,表示页面内容意外移动的程度
我们可以使用 Google 提供的 Web Vitals 库来监测这些指标:
import {getLCP, getFID, getCLS} from 'web-vitals';function sendToAnalytics({name, value, id}) {// 将指标数据发送到分析服务const body = JSON.stringify({name, value, id});navigator.sendBeacon('/analytics', body);console.log(`${name}: ${value}`);
}// 监测并报告核心 Web 指标
getCLS(sendToAnalytics); // 累积布局偏移
getFID(sendToAnalytics); // 首次输入延迟
getLCP(sendToAnalytics); // 最大内容绘制// 获取其他有用指标
getFCP(sendToAnalytics); // 首次内容绘制
getTTFB(sendToAnalytics); // 首字节时间
这些指标的目标值:
指标 | 良好 | 需要改进 | 较差 |
---|---|---|---|
LCP | ≤2.5秒 | ≤4秒 | >4秒 |
FID | ≤100ms | ≤300ms | >300ms |
CLS | ≤0.1 | ≤0.25 | >0.25 |
除了核心 Web 指标,还有其他重要的性能指标:
- 首次绘制(FP, First Paint):浏览器首次渲染任何视觉内容的时间点
- 首次内容绘制(FCP, First Contentful Paint):浏览器首次渲染任何文本、图像、背景或 canvas 的时间点
- 首次有意义绘制(FMP, First Meaningful Paint):页面主要内容变得可见的时间点
- 可交互时间(TTI, Time to Interactive):页面完全可交互的时间点
- 总阻塞时间(TBT, Total Blocking Time):FCP 与 TTI 之间主线程阻塞的总时间
使用 Performance API 实时监控
浏览器的 Performance API 提供了精确测量页面性能的能力:
// 使用 Performance API 测量关键渲染路径
// 在关键渲染开始时
performance.mark('critical-render-start');// 渲染关键内容
renderCriticalContent();// 在关键渲染结束时
performance.mark('critical-render-end');// 计算关键渲染时间
performance.measure('critical-render-time','critical-render-start','critical-render-end'
);// 获取并分析测量结果
const measures = performance.getEntriesByType('measure');
measures.forEach(measure => {console.log(`${measure.name}: ${measure.duration}ms`);// 将数据发送到分析服务sendToAnalytics({name: measure.name,value: measure.duration});
});
对于复杂应用,我们可以使用 Performance API 创建自定义性能标记来测量特定功能的性能:
// 测量组件渲染性能
function renderComponent(data) {performance.mark(`${data.id}-render-start`);// 组件渲染逻辑...performance.mark(`${data.id}-render-end`);performance.measure(`${data.id}-render-time`,`${data.id}-render-start`,`${data.id}-render-end`);
}// 测量数据加载性能
async function fetchData(endpoint) {performance.mark(`${endpoint}-fetch-start`);const response = await fetch(endpoint);const data = await response.json();performance.mark(`${endpoint}-fetch-end`);performance.measure(`${endpoint}-fetch-time`,`${endpoint}-fetch-start`,`${endpoint}-fetch-end`);return data;
}
Performance API 还允许我们收集资源加载性能指标:
// 分析资源加载性能
function analyzeResourcePerformance() {// 获取所有资源加载条目const resources = performance.getEntriesByType('resource');// 按资源类型分类const resourcesByType = resources.reduce((acc, resource) => {const type = resource.initiatorType || 'other';if (!acc[type]) acc[type] = [];acc[type].push(resource);return acc;}, {});```javascript// 分析每种资源类型的加载性能for (const [type, items] of Object.entries(resourcesByType)) {// 计算平均加载时间const avgDuration = items.reduce((sum, item) => sum + item.duration, 0) / items.length;console.log(`平均 ${type} 加载时间: ${avgDuration.toFixed(2)}ms`);// 找出加载最慢的资源const slowest = items.sort((a, b) => b.duration - a.duration)[0];console.log(`最慢的 ${type}: ${slowest.name}, 用时 ${slowest.duration.toFixed(2)}ms`);// 分析阻塞时间const totalBlockingTime = items.reduce((sum, item) => {// 连接时间 + 请求/响应时间const blockingTime = (item.connectEnd - item.connectStart) + (item.responseEnd - item.requestStart);return sum + blockingTime;}, 0);console.log(`${type} 总阻塞时间: ${totalBlockingTime.toFixed(2)}ms`);}
}// 页面加载完成后分析资源性能
window.addEventListener('load', () => {// 给浏览器一些时间完成最终处理setTimeout(analyzeResourcePerformance, 1000);
});
真实用户监控(RUM)与合成监控
全面的性能监控通常结合两种方法:
- 真实用户监控(RUM, Real User Monitoring):收集实际用户访问网站时的性能数据
- 合成监控(Synthetic Monitoring):通过模拟用户访问来测试性能
// 真实用户监控实现示例
class PerformanceMonitor {constructor(sampleRate = 0.1) { // 默认采样 10% 的访问this.sampleRate = sampleRate;this.metrics = {navigationTiming: {},webVitals: {},resources: [],errors: [],customMeasures: {}};// 只对采样用户进行监控this.shouldMonitor = Math.random() <= this.sampleRate;if (!this.shouldMonitor) return;this.setupMonitoring();}setupMonitoring() {// 收集导航计时数据this.captureNavigationTiming();// 监控核心 Web 指标this.captureWebVitals();// 监控资源加载性能this.captureResourceTiming();// 监控 JavaScript 错误this.captureErrors();// 设置性能条目观察器this.observePerformanceEntries();// 页面卸载前发送数据window.addEventListener('beforeunload', () => this.sendMetrics());}captureNavigationTiming() {window.addEventListener('load', () => {// 等待加载完成后采集数据setTimeout(() => {const perfData = window.performance.timing;const navStart = perfData.navigationStart;this.metrics.navigationTiming = {// DNS 查询时间dnsTime: perfData.domainLookupEnd - perfData.domainLookupStart,// TCP 连接时间tcpTime: perfData.connectEnd - perfData.connectStart,// 请求响应时间requestTime: perfData.responseEnd - perfData.requestStart,// DOM 解析时间domParsingTime: perfData.domComplete - perfData.domLoading,// 页面总加载时间pageLoadTime: perfData.loadEventEnd - navStart,// 首字节时间ttfb: perfData.responseStart - perfData.requestStart,// DOM 交互时间domInteractive: perfData.domInteractive - navStart};}, 0);});}captureWebVitals() {// 使用 web-vitals 库import('web-vitals').then(({ getLCP, getFID, getCLS, getFCP, getTTFB }) => {getCLS(metric => this.metrics.webVitals.cls = metric.value);getFID(metric => this.metrics.webVitals.fid = metric.value);getLCP(metric => this.metrics.webVitals.lcp = metric.value);getFCP(metric => this.metrics.webVitals.fcp = metric.value);getTTFB(metric => this.metrics.webVitals.ttfb = metric.value);});}captureResourceTiming() {// 采集资源计时数据window.addEventListener('load', () => {setTimeout(() => {const resources = performance.getEntriesByType('resource');this.metrics.resources = resources.map(resource => ({name: resource.name,type: resource.initiatorType,duration: resource.duration,size: resource.transferSize,startTime: resource.startTime}));}, 0);});}captureErrors() {// 监听 JavaScript 错误window.addEventListener('error', event => {this.metrics.errors.push({message: event.message,source: event.filename,lineno: event.lineno,colno: event.colno,timestamp: Date.now()});});// 监听未捕获的 Promise 错误window.addEventListener('unhandledrejection', event => {this.metrics.errors.push({message: event.reason?.message || 'Unhandled Promise rejection',timestamp: Date.now()});});}observePerformanceEntries() {// 观察性能条目const observer = new PerformanceObserver(list => {list.getEntries().forEach(entry => {// 记录用户交互if (entry.entryType === 'first-input') {this.metrics.firstInput = {delay: entry.processingStart - entry.startTime,processingTime: entry.processingEnd - entry.processingStart,target: entry.target};}// 记录长任务if (entry.entryType === 'longtask') {if (!this.metrics.longTasks) this.metrics.longTasks = [];this.metrics.longTasks.push({duration: entry.duration,startTime: entry.startTime});}// 记录布局偏移if (entry.entryType === 'layout-shift' && !entry.hadRecentInput) {if (!this.metrics.layoutShifts) this.metrics.layoutShifts = [];this.metrics.layoutShifts.push({value: entry.value,startTime: entry.startTime});}});});// 观察各种类型的性能条目observer.observe({ entryTypes: ['first-input', 'longtask', 'layout-shift'] });}// 添加自定义性能测量addCustomMeasure(name, value) {if (!this.shouldMonitor) return;this.metrics.customMeasures[name] = value;}// 发送收集的指标到分析服务器sendMetrics() {if (!this.shouldMonitor) return;// 添加设备和环境信息this.metrics.context = {userAgent: navigator.userAgent,deviceMemory: navigator.deviceMemory,hardwareConcurrency: navigator.hardwareConcurrency,connectionType: navigator.connection?.effectiveType,url: window.location.href,timestamp: Date.now()};// 使用 Beacon API 发送数据(不阻塞页面卸载)navigator.sendBeacon('/api/performance', JSON.stringify(this.metrics));}
}// 初始化性能监控(采样 10% 的访问)
const perfMonitor = new PerformanceMonitor(0.1);// 使用示例:添加自定义性能指标
function fetchUserData() {const startTime = performance.now();return fetch('/api/user').then(response => response.json()).finally(() => {const endTime = performance.now();perfMonitor.addCustomMeasure('userDataFetchTime', endTime - startTime);});
}
合成监控与实际用户监控相辅相成:合成监控提供了可重复的基准测试,可以在问题影响实际用户前检测到;而实际用户监控反映了用户在各种真实条件下的体验。理想的性能监控方案应同时使用这两种方法。
浏览器兼容性与优雅降级
随着 Web 标准的发展,新特性不断涌现,但不同浏览器对这些特性的支持程度不同。在实施性能优化时,需要考虑兼容性和提供优雅降级机制。
特性检测与降级方案
特性检测是处理兼容性问题的最佳实践,它允许我们在支持特定功能的浏览器中使用该功能,同时为不支持的浏览器提供替代方案:
// 检测原生懒加载支持
if ('loading' in HTMLImageElement.prototype) {// 浏览器支持原生懒加载document.querySelectorAll('img[data-src]').forEach(img => {img.src = img.dataset.src;img.loading = 'lazy';});
} else {// 降级方案:使用 JavaScript 懒加载库loadScript('/js/lazysizes.min.js').then(() => {// 初始化 lazysizesdocument.querySelectorAll('img[data-src]').forEach(img => {img.classList.add('lazyload');});});
}// 检测 Intersection Observer 支持
if (!('IntersectionObserver' in window)) {// 加载 polyfillloadScript('https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver').then(() => {// 初始化基于 IntersectionObserver 的功能initLazyLoading();});
} else {// 直接初始化initLazyLoading();
}// 检测 content-visibility 支持
if ('contentVisibility' in document.documentElement.style) {// 支持 content-visibility,应用优化applyContentVisibility();
} else {// 不支持,应用替代优化策略applyAlternativeOptimization();
}// 工具函数:动态加载脚本
function loadScript(src) {return new Promise((resolve, reject) => {const script = document.createElement('script');script.src = src;script.onload = resolve;script.onerror = reject;document.body.appendChild(script);});
}
使用 Polyfill 和特性检测库
对于关键功能,我们可以使用 Polyfill 提供缺失特性的模拟实现:
<!-- 使用 polyfill.io 按需加载 polyfill -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver,fetch,Promise,Array.from"></script><!-- 或者使用模块化方式按需加载 -->
<script>// 检测并按需加载 polyfill(function() {const features = [];if (!('IntersectionObserver' in window)) {features.push('IntersectionObserver');}if (!('fetch' in window)) {features.push('fetch');}if (!('Promise' in window)) {features.push('Promise');}if (features.length > 0) {const script = document.createElement('script');script.src = `https://polyfill.io/v3/polyfill.min.js?features=${features.join(',')}`;document.head.appendChild(script);}})();
</script>
对于复杂项目,我们可以使用特性检测库如 Modernizr:
<!-- 使用定制的 Modernizr 检测浏览器特性 -->
<script src="modernizr-custom.js"></script>
<script>if (Modernizr.intersectionobserver) {// 使用 IntersectionObserver} else {// 使用备选方案}// 根据特性应用不同的 CSSif (Modernizr.cssgrid) {document.documentElement.classList.add('cssgrid');} else {document.documentElement.classList.add('no-cssgrid');}
</script><style>/* 基于特性应用不同样式 */.cssgrid .container {display: grid;grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));gap: 20px;}.no-cssgrid .container {display: flex;flex-wrap: wrap;}.no-cssgrid .item {flex: 0 0 calc(33.333% - 20px);margin: 10px;}
</style>
渐进增强与平稳降级策略
在性能优化中,应采用渐进增强(Progressive Enhancement)和平稳降级(Graceful Degradation)的策略:
渐进增强:先确保基本功能在所有浏览器中正常工作,然后为现代浏览器添加增强特性:
// 基本图片加载功能 - 适用于所有浏览器
function loadImages() {document.querySelectorAll('img[data-src]').forEach(img => {img.src = img.dataset.src;});
}// 增强功能 - 现代浏览器的懒加载
function enhancedImageLoading() {if ('IntersectionObserver' in window) {const imageObserver = new IntersectionObserver((entries, observer) => {entries.forEach(entry => {if (entry.isIntersecting) {const img = entry.target;img.src = img.dataset.src;observer.unobserve(img);}});});document.querySelectorAll('img[data-src]').forEach(img => {imageObserver.observe(img);});} else {// 降级到基本功能loadImages();}
}// 根据浏览器能力选择功能
enhancedImageLoading();
平稳降级:为现代浏览器设计最佳体验,然后为旧浏览器提供备选方案:
// 现代浏览器的优化首屏加载
function optimizeFirstPaint() {if ('contentVisibility' in document.documentElement.style) {// 使用 content-visibility 优化document.querySelectorAll('.below-fold').forEach(section => {section.style.contentVisibility = 'auto';section.style.containIntrinsicSize = '0 500px';});} else if ('IntersectionObserver' in window) {// 降级:使用 IntersectionObserver 延迟处理离屏内容const observer = new IntersectionObserver(entries => {entries.forEach(entry => {if (entry.isIntersecting) {entry.target.classList.add('visible');observer.unobserve(entry.target);}});});document.querySelectorAll('.below-fold').forEach(section => {section.style.opacity = '0';section.style.transition = 'opacity 0.3s ease';observer.observe(section);});} else {// 继续降级:使用延时函数setTimeout(() => {document.querySelectorAll('.below-fold').forEach(section => {section.classList.add('visible');});}, 1000); // 等待首屏渲染后再显示}
}// CSS 配合降级策略
document.head.insertAdjacentHTML('beforeend', `<style>.below-fold.visible {opacity: 1 !important;}</style>
`);// 页面加载后应用优化
document.addEventListener('DOMContentLoaded', optimizeFirstPaint);
总结与展望
综合优化策略
优化 HTML 结构提升 Web 性能是一个多层次的过程,需要综合考虑多种因素:
-
优化关键渲染路径:
- 内联关键 CSS
- 异步加载非关键 CSS 和 JavaScript
- 优先渲染首屏内容
-
有效利用预加载:
- 使用
preload
、prefetch
、preconnect
等资源提示 - 结合 HTTP/2 服务器推送最大化加载效率
- 预测用户行为,提前加载可能需要的资源
- 使用
-
延迟加载非首屏内容:
- 实现图片和多媒体懒加载
- 使用
content-visibility
延迟渲染 - 按需加载非关键脚本和数据
-
最小化回流与重绘:
- 批量 DOM 操作
- 避免强制同步布局
- 使用 CSS 转换和不触发回流的属性
-
应用现代浏览器优化特性:
- 使用原生懒加载
- 利用 IntersectionObserver 优化滚动相关功能
- 应用
fetchpriority
控制资源加载优先级
这些策略相互配合,形成一个全面的性能优化框架。最有效的优化方法取决于具体项目的特点和目标用户群的浏览器支持情况。
性能优化的持续迭代
性能优化不是一次性工作,而是一个持续改进的过程。建立有效的性能监控系统,收集真实用户数据,定期评估性能指标,是持续优化的基础。
将性能指标纳入开发流程,设立性能预算,在开发早期就考虑性能因素,可以避免性能问题的积累。同时,追踪业务指标与性能指标的关系,量化性能优化带来的实际业务价值,有助于获取更多资源投入到性能优化工作中。
未来性能优化趋势
Web 技术不断发展,新的性能优化方法也在不断涌现:
-
核心 Web 指标的演进:
- Google 的核心 Web 指标不断更新,如最近提出的 INP(Interaction to Next Paint) 指标,更加关注交互响应性
- 性能优化将更加聚焦于用户体验的实际感受,而不仅是技术指标
-
AI 辅助的性能优化:
- AI 可以分析用户行为模式,预测用户可能的下一步操作,实现更智能的预加载
- 自动化工具可以根据用户设备和网络条件,动态调整内容加载策略
-
多层次缓存策略:
- 结合 Service Worker、IndexedDB 和 Cache API 实现离线优先应用
- 利用 Edge Computing 在网络边缘节点缓存内容,减少延迟
-
高级图像优化技术:
- 自适应图像格式和分辨率,根据设备能力和网络条件调整
- 使用 AVIF、WebP2 等下一代图像格式,提供更高压缩率
-
区块链和去中心化:
- 去中心化存储和分发内容,减少中心服务器负载
- Web3 应用中的性能优化将成为新的研究领域
作为前端工程师,持续学习和应用这些新兴技术,将性能优化视为开发过程的核心部分,不仅能提升用户体验,也能展示专业技术能力和全局意识。
个人实践经验与总结
从理论到实践的转化
在我们负责的项目当中,性能优化始终是一个循序渐进的过程,需要结合理论知识和实际情况。以下是一些实践中的关键经验:
-
从数据出发:在进行任何优化前,首先建立基准测试和监控系统,收集实际性能数据。只有了解当前瓶颈,才能有针对性地进行优化。
-
小步快跑:将大型优化任务分解为小的、可独立验证的变更。每次只修改一个方面,验证其效果后再继续下一步。这样可以清晰地了解每项优化措施的实际效果。
-
关注投入产出比:不同的优化措施所需的工程投入和带来的性能提升各不相同。优先实施那些投入小但效果显著的"低垂果实",如图片优化、资源压缩等。
-
用户为中心:不要过度关注技术指标而忽视实际用户体验。有时,感知性能比实际性能更重要,如使用骨架屏、进度指示等技术增强用户感知。
克服常见挑战
在实施性能优化时,常见的挑战包括:
-
遗留代码的优化:面对大型遗留代码库,全面重构往往不现实。这种情况下,可以采用"优化包装"策略:
-
在不修改核心代码的情况下,添加预加载层
-
在不修改核心代码的情况下,添加预加载层
-
实施缓存策略减少服务器压力
-
使用 Service Worker 拦截和优化请求
-
-
多方协作的协调:性能优化往往涉及前端、后端、设计、产品等多个团队的协作。建立明确的性能预算和指标,将性能目标量化,有助于跨团队协作。
-
持续维护的挑战:随着项目迭代,性能优化成果容易被新功能开发稀释。建立自动化性能测试和监控系统,将性能指标纳入 CI/CD 流程,可以及时发现性能退化问题。
新技术应用经验
在最近的项目中,应用一些新兴技术取得了良好效果:
-
图像 CDN 与自适应图像:使用如 Cloudinary、Imgix 等服务,根据设备特性和网络条件自动优化图像:
<!-- 使用图像 CDN 的响应式图像 --> <img src="https://res.cloudinary.com/demo/image/upload/w_auto,q_auto,f_auto/sample.jpg"sizes="(max-width: 600px) 100vw, 50vw"alt="描述" >
-
模块联邦与微前端:在大型应用中,使用 Webpack 5 的模块联邦功能,将应用拆分为多个独立部署的微前端,实现更精细的按需加载:
// webpack.config.js new ModuleFederationPlugin({name: 'host',remotes: {shop: 'shop@http://localhost:3002/remoteEntry.js',},shared: ['react', 'react-dom'], })
-
优化 CSS 渲染性能:使用 CSS containment 和层合成优化:
.optimized-section {contain: content;will-change: transform;transform: translateZ(0); }
前端性能优化的未来方向
展望未来,前端性能优化将在以下几个方向继续发展:
-
Web 组装技术(WebAssembly):随着 WebAssembly 的成熟,越来越多的计算密集型任务可以从 JavaScript 迁移到更高效的 Wasm 模块,显著提升性能。
-
边缘计算与边缘渲染:在 CDN 边缘节点进行局部渲染和计算,减少主服务器负载,降低延迟。如使用 Cloudflare Workers、Vercel Edge Functions 等。
-
以隐私为中心的分析:随着第三方 Cookie 的淘汰,基于浏览器原生 API 和第一方数据的性能分析将更为重要。
-
原子化 CSS 与零运行时框架:如 Tailwind CSS、Windi CSS 等原子化 CSS 框架,以及 Solid.js、Svelte 等零(或低)运行时框架,将进一步减少前端性能开销。
-
实时性能调整:基于用户设备、网络状况和使用模式,实时调整应用的加载和渲染策略,实现真正的自适应性能优化。
最后的话
HTML 与 Web 性能优化是一个融合技术与艺术的领域,它需要扎实的技术基础,敏锐的问题洞察,以及不断实践和创新的精神。通过优化 HTML 结构、精心设计资源加载策略、实施延迟加载技术、最小化回流与重绘,以及利用现代浏览器特性,我们可以创造出既快速又流畅的 Web 体验。
作为前端工程师,性能优化不仅是技术能力的体现,也是对用户体验的尊重和负责。在追求功能与美观的同时,始终将性能作为核心指标之一,才能打造出真正卓越的 Web 产品。
每一毫秒的优化,都可能为用户节省宝贵的时间;每一个性能改进,都可能为企业带来实质性的商业价值。在日益竞争的数字世界中,卓越的性能不再是锦上添花,而是成功的必要条件。
参考资源
- Web Vitals - Google 定义的核心 Web 指标
- MDN Web 性能 - 全面的 Web 性能文档
- Chrome 开发者工具性能分析 - 使用 Chrome DevTools 分析性能
- The Cost of JavaScript - JavaScript 性能成本分析
- High Performance Browser Networking - Ilya Grigorik 的经典著作
- Front-End Performance Checklist - 前端性能清单
- Web Performance Calendar - 性能优化专家分享的最佳实践
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻
相关文章:
HTML与Web 性能优化:构建高速响应的现代网站
HTML 与 Web 性能优化:构建高速响应的现代网站 引言 随着互联网用户对网站加载速度期望的不断提高,前端性能优化已经成为现代 Web 开发的核心竞争力。据 Google 研究表明,页面加载时间每增加 1 秒,用户跳出率就会增加 32%。用户…...
模型 观测者效应
系列文章分享模型,了解更多👉 模型_思维模型目录。观察即影响,存在因注视而变。 1 观测者效应的应用 1.1 工业心理学—霍桑实验中的生产效率谜题 行业背景:20世纪20年代西方电气公司霍桑工厂,研究者试图通过优化照明…...
Ubuntu启动SMB(Samba)服务步骤
目录 1.基本的Samba服务器搭建流程主要分为四个步骤。 2.Samba工作流程: 3.解读主要配置文件smb.conf 4.开始安装Samba 5.检查Samba服务状态 6.创建Samba共享文件夹 7.配置Samba文件以及设置Samba用户密码 8.重启Samba服务器 9.关闭防火墙 10.Linux客户端…...
使用react的ant-design-pro框架写一个地图组件,可以搜索地图,可以点击地图获取点击的位置及经纬度
首先,先创建一个地图页面,用于显示地图组件,我是在pages文件中创建了一个mapSearch组件。 然后在routes.ts中注册页面。 {path: /mapSearch,name: mapSearch,icon: smile,component: ./mapSearch,}, 第三步就是使用高德地图来创建地图。 关键…...
【每日八股】复习计算机网络 Day4:TCP 协议的其他相关问题
文章目录 昨日内容复习已经建立了 TCP 连接,客户端突然出现故障怎么办?什么时候用长连接?短连接?TCP 的半连接队列与全连接队列?什么是 SYN 攻击?如何避免?TIME_WAIT 的作用?过多如何…...
Git远程操作与标签管理
目录 1.理解分布式版本控制系统 2.远程仓库 3.新建远程仓库 4.克隆远程仓库 5.向远程仓库推送 6.拉取远程仓库 7.配置Git 7.1.忽略特殊文件 7.2.给命令配置别名 8.标签管理 8.1.理解标签 8.2.创建标签 8.3.操作标签 1.理解分布式版本控制系统 Git是目前世界上…...
Element Plus消息通知体系深度解析:从基础到企业级实践
一、核心组件与技术定位 Element Plus的消息通知体系由三个核心组件构成:ElMessage(全局提示)、ElNotification(通知弹窗)和ElMessageBox(交互式对话框)。这套体系的设计目标是为开发者提供轻量…...
SpringCloud组件——Eureka
一.背景 1.问题提出 我们在一个父项目下写了两个子项目,需要两个子项目之间相互调用。我们可以发送HTTP请求来获取我们想要的资源,具体实现的方法有很多,可以用HttpURLConnection、HttpClient、Okhttp、 RestTemplate等。 举个例子&#x…...
[Godot] C#2D平台游戏基础移动和进阶跳跃代码
本文章给大家分享一下如何实现基本的移动和进阶的跳跃(跳跃缓冲、可变跳跃、土狼时间)以及相对应的重力代码,大家可以根据自己的需要自行修改 实现效果 场景搭建 因为Godot不像Unity,一个节点只能绑定一个脚本,所以我…...
C语言对n进制的处理
先看一道题目: 从键盘获取一个正整数,如果把它转为16进制的数字,那么它是一个几位数呢?如果把它转为28进制又是一个几位数呢? 在讲这个题目之前,我们先要了解进制转换 什么是进制转换? 简单来说,进制就是数位的表示方法。 十进制(常用&am…...
rk3568main.cc解析
rk3568main.cc解析 前言解析总结前言 正点原子rk3568学习,rk官方RKNN_MODEL_ZOO文件中 rknn_model_zoo-main/examples/mobilenet/cpp/main.cc 从执行命令:./build-linux.sh -t rk3568 -a aarch64 -d mobilenet 到: cmake ../../examples/mobilenet/cpp \-DTARGET_SOC=rk3…...
【白雪讲堂】[特殊字符]内容战略地图|GEO优化框架下的内容全景布局
📍内容战略地图|GEO优化框架下的内容全景布局 1️⃣ 顶层目标:GEO优化战略 目标关键词: 被AI理解(AEO) 被AI优先推荐(GEO) 在关键场景中被AI复读引用 2️⃣ 三大引擎逻辑&#x…...
S32K144学习(16)-Bootloader
1.什么是bootloader Bootloader(引导加载程序) 是存储在设备非易失性存储器(如 ROM、Flash)中的一段特殊程序,负责在设备上电后初始化硬件、加载操作系统(OS)或用户应用程序,并最终…...
反素数c++
先上代码 #include<bits/stdc.h> using namespace std; typedef long long ll; ll n; ll p[]{2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53}; int maxd,maxval; void dfs(int pl,ll tmp,int num,int up){ if((num>maxd)||(nummaxd&&maxval>tmp)){ …...
C++ linux打包运行方案(cmake)
文章目录 背景动态库打包方案动态库转静态库动态库打到软件包中 运行 背景 使用C编写的一个小项目,需要打包成ubuntu下的可执行文件,方便分发给其他ubuntu执行,因为docker镜像方案过于臃肿,所以需要把项目的动态库都打在软件包中…...
JavaScript 渲染内容爬取实践:Puppeteer 进阶技巧
进一步探讨如何使用 Puppeteer 进行动态网页爬取,特别是如何等待页面元素加载完成、处理无限滚动加载、单页应用的路由变化以及监听接口等常见场景。 一、等待页面元素加载完成 在爬取动态网页时,确保页面元素完全加载是获取完整数据的关键。Puppeteer…...
AI数字人:元宇宙舞台上的闪耀新星(7/10)
摘要:AI数字人作为元宇宙核心角色,提升交互体验,推动内容生产变革,助力产业数字化转型。其应用场景涵盖虚拟社交、智能客服、教育、商业营销等,面临技术瓶颈与行业规范缺失等挑战,未来有望突破技术限制&…...
测试基础笔记第九天
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、数据类型和约束1.数据类型2.约束3.主键4.不为空5.唯一6.默认值 二、数据库操作1.创建数据库2.使用数据库3.修改数据库4.删除数据库和查看所有数据库5.重点&…...
C++抽象基类定义与使用
在 C 中,抽象基类(Abstract Base Class, ABC) 是一种特殊的类,用于定义接口规范和约束派生类的行为。它通过纯虚函数(Pure Virtual Function)强制要求派生类实现特定功能,自身不能被实例化。以下…...
20.4 显示数据库数据
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的 20.4.1 设计时进行简单绑定 【例 20.22】【项目:code20-022】设计时关联数据库。 设计时设置DataGridView的DataSource属…...
PyTorch 多 GPU 入门:深入解析 nn.DataParallel 的工作原理与局限
当你发现单个 GPU 已经无法满足你训练庞大模型或处理海量数据的需求时,利用多 GPU 进行并行训练就成了自然的选择。PyTorch 提供了几种实现方式,其中 torch.nn.DataParallel (简称 DP) 因其使用的便捷性,常常是初学者接触多 GPU 训练的第一站…...
UDP协议理解
文章目录 UDP协议理解UDP 协议的特点:UDP协议图示UDP 的头部结构:UDP数据传输图示 UDP 的应用场景:TCP 与UDP对比UDP的传输丢包和顺序错乱问题(了解)丢包的解决方法:顺序错乱的解决方法:综合应用…...
微信小程序拖拽排序有效果图
效果图 .wxml <view class"container" style"--w:{{w}}px;" wx:if"{{location.length}}"><view class"container-item" wx:for"{{list}}" wx:key"index" data-index"{{index}}"style"--…...
算力网络的早期有关论文——自用笔记
2023年底至2024年初阅读有关论文的自用笔记,作为参考。 算力网络架构 https://baijiahao.baidu.com/s?id1727377583404975414&wfrspider&forpc think¬e 是否可以和cpu进程调度联系。 目前:看一些综述深一步了解背景和发展现状,完善认…...
卷积神经网络基础(四)
今天我们继续学习各个激活函数层的实现过程。 目录 5.2 Sigmoid层 六、Affine/Softmax层实现 6.1 Affine层 6.2 批处理版本 5.2 Sigmoid层 sigmoid函数的表达式如下: 用计算图表示的话如下: 计算过程稍微有些复杂,且这里除了乘法和加法…...
【MySQL数据库】表的约束
目录 1,空属性 2,默认值 3,列描述 4,zerofill 5,主键primary key 6,自增长auto_increment 7,唯一键unique 8,外键foreign key 在MySQL中,表的约束是指用于插入的…...
网络威胁情报 | Friday Overtime Trooper
本文将分别从两个环境出发,以实践来体验利用威胁情报分析可疑文件的过程。 Friday Overtime 现在你是一位安全分析人员,正在美美等待周五过去,但就在即将下班之时意外发生了:你的客户发来求助,说他们发现了一些可疑文…...
GPIO(通用输入输出端口)详细介绍
一、基本概念 GPIO(General - Purpose Input/Output)即通用输入输出端口,是微控制器(如 STM32 系列)中非常重要的一个外设。它是一种软件可编程的引脚,用户能够通过编程来控制这些引脚的输入或输出状态。在…...
学习笔记——《Java面向对象程序设计》-继承
参考教材: Java面向对象程序设计(第3版)微课视频版 清华大学出版社 1、定义子类 class 子类名 extends 父类名{...... }如: class Student extends People{...... } (1)如果一个类的声明中没有extends关…...
基于javaweb的SpringBoot校园失物招领系统设计与实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…...
什么事Nginx,及使用Nginx部署vue项目(非服务器Nginx压缩包版)
什么是 Nginx? Nginx(发音为 “engine-x”)是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。它以其高性能、高并发处理能力和低资源消耗而闻名。以下是 Nginx 的主要特性和用途: 主要特性 高性能和高并发 Nginx 能够处理大量并发连接,适合高…...
nodejs使用require导入npm包,开发依赖和生产依赖 ,全局安装
nodejs使用require导入npm包,开发依赖和生产依赖 ,全局安装 ✅ 一、Node.js 中使用 require() 导入 npm 包 // 导入第三方包(例如 axios) const axios require(axios);// 使用 axios.get(https://api.example.com).then(res &g…...
CSS在线格式化 - 加菲工具
CSS在线格式化 打开网站 加菲工具 选择“CSS在线格式化” 或者直接访问 https://www.orcc.top/tools/css 输入CSS代码,点击左上角的“格式化”按钮 得到格式化后的结果...
图片转base64 - 加菲工具 - 在线转换
图片转base64 - 加菲工具 先进入“加菲工具” 网 打开 https://www.orcc.top, 选择 “图片转base64”功能 选择需要转换的图片 复制 点击“复制”按钮,即可复制转换好的base64编码数据,可以直接用于img标签。...
性能比拼: Redis vs Dragonfly
本内容是对知名性能评测博主 Anton Putra Redis vs Dragonfly Performance (Latency - Throughput - Saturation) 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准 在本视频中,我们将对比 Redis 和 Dragonfly。我们将观察 set 与 get 操作的延迟ÿ…...
如何收集用户白屏/长时间无响应/接口超时问题
想象一下这样的场景:一位用户在午休时间打开某电商应用,准备购买一件心仪已久的商品。然而,页面加载了数秒后依然是一片空白,或者点击“加入购物车”按钮后没有任何反馈,甚至在结算时接口超时导致订单失败。用户的耐心被迅速消耗殆尽,关闭应用,转而选择了竞争对手的产品…...
来啦,烫,查询达梦表占用空间
想象一下oracle,可以查dba_segments,但是这个不可靠(达梦官方连说明书都没有) 先拼接一个sql set lineshow off SELECT SELECT ||||OWNER|||| AS OWNER,||||TABLE_NAME|||| AS TABLE_NAME,TABLE_USED_SPACE(||||OWNER||||,||||T…...
# 利用迁移学习优化食物分类模型:基于ResNet18的实践
利用迁移学习优化食物分类模型:基于ResNet18的实践 在深度学习的众多应用中,图像分类一直是一个热门且具有挑战性的领域。随着研究的深入,我们发现利用预训练模型进行迁移学习是一种非常有效的策略,可以显著提高模型的性能&#…...
AT24C02芯片简介:小巧强大的串行EEPROM存储器
一、AT24C02概述 AT24C02是一款2K位(即256字节)的串行EEPROM芯片,采用IC(Inter-Integrated Circuit)总线进行通信,适合低功耗、小容量存储需求。 主要特性: 项目 参数 存储容量 2Kb&#x…...
【Vue】状态管理(Vuex、Pinia)
个人主页:Guiat 归属专栏:Vue 文章目录 1. 状态管理概述1.1 什么是状态管理1.2 为什么需要状态管理 2. Vuex基础2.1 Vuex核心概念2.1.1 State2.1.2 Getters2.1.3 Mutations2.1.4 Actions2.1.5 Modules 2.2 Vuex辅助函数2.2.1 mapState2.2.2 mapGetters2.…...
施磊老师基于muduo网络库的集群聊天服务器(四)
文章目录 实现登录业务登录业务代码补全数据库接口:查询,更新状态注意学习一下里面用到的数据库api测试与问题**问题1:****问题2:** 用户连接信息与线程安全聊天服务器是长连接服务器如何找到用户B的连接?在业务层存储用户的连接信息多线程安全问题加锁! 处理客户端…...
深度学习-全连接神经网络(过拟合,欠拟合。批量标准化)
七、过拟合与欠拟合 在训练深层神经网络时,由于模型参数较多,在数据量不足时很容易过拟合。而正则化技术主要就是用于防止过拟合,提升模型的泛化能力(对新数据表现良好)和鲁棒性(对异常数据表现良好)。 1. 概念认知 …...
访问Maven私服的教程
1.首先准备好maven私服的启动器,到bin目录下启动: 2.等待加载,加载过程比较长: 3.访问端口号: 4.仓库简介: 5.在maven的setting中 servers配置信息(设置私服访问的密码): 6.配置私服仓库地址: 7.配置上传地址(私服地址): 8.在自己的副项…...
Linux系统编程 day9 SIGCHLD and 线程
SIGCHLD信号 只要子进程信号发生改变,就会产生SIGCHLD信号。 借助SIGCHLD信号回收子进程 回收子进程只跟父进程有关。如果不使用循环回收多个子进程,会产生多个僵尸进程,原因是因为这个信号不会循环等待。 #include<stdio.h> #incl…...
Linux 内核中 cgroup 子系统 cpuset 是什么?
cpuset 是 Linux 内核中 cgroup(控制组) 的一个子系统,用于将一组进程(或任务)绑定到特定的 CPU 核心和 内存节点(NUMA 节点)上运行。它通过限制进程的 CPU 和内存资源的使用范围,优…...
乐视系列玩机---乐视2 x520 x528等系列线刷救砖以及刷写第三方twrp 卡刷第三方固件步骤解析
乐视2 x520 x528 x526等,搭载了高通骁龙652处理器,骁龙652的GPU性能甚至优于前一年的骁龙810,配备了3GB RAM和32GB ROM的存储空间, 通过博文了解💝💝💝 1💝💝💝-----详细解析乐视2 x520系列黑砖线刷救砖的步骤 2💝💝💝----官方两种更新卡刷步骤以及刷…...
电容加速电路!
大家好,我是记得诚。 今天分享一个小电路:电容加速电路。 下面是一个普通的三极管开关电路,区别是多了一个C1,C1被称为加速电容。作用是:加速三极管VT1的开通和截止,做到快开快关。 工作原理:…...
MCP Host、MCP Client、MCP Server全流程实战
目录 准备工作 MCP Server 实现 调试工作 MCP Client 实现 MCP Host 配置 第一步:配置支持 function calling的 LLM 第二步:添加MCP Server 一般有两种方式,第一种json配置,第二种直接是Command形式,我这里采用Command形式 第三步:使用MCP Server 准备工作 安装…...
Win10一体机(MES电脑设置上电自动开机)
找个键盘,带线的那种,插到电脑上,电脑开机;连续点按F11;通过↑↓键选择Enter Setup 然后回车; 选择 smart settings ; 选择 Restore AC Power Loss By IO 回车; 将prower off 改为…...
强化学习和微调 区别如下
强化学习和微调 区别如下 定义与概念 强化学习**:是一种机器学习范式,强调智能体(agent)如何在环境中采取一系列行动,以最大化累积奖励**。智能体通过与环境进行交互,根据环境反馈的奖励信号来学习最优的行为策略。例如,机器人通过不断尝试不同的动作来学习如何在复杂环…...