大师课:前端,我眼看它出现、它成长、它成熟,却远远不是结束。 因此,我想说给你听,让你对我们身处的行业,有个大框架式的认知,知道:它是怎么来的,从而去判断,它要往哪里去。
前言:作为一位拥有13年经验的资深前端开发者,我深知性能优化在实践上的难点和痛点。一方面,业务需要更好的打开率、更高的转化率,一方面,扑面而来的优化手段却又千篇一律,好像知道很多,但又不知道怎么着手拿效果。
问前端 13年大厂前端资深大佬在线回答问题,我不唬人,是真心为你们服务,想传帮带,把大厂的经验和思维方式通过这种方式告诉你们
1对1量身制定个人发展方案,提供业界专业建议,和面试指导。已帮助服务过上百名毕业生;
- 适合人群:应届毕业生找方向、前端小白打怪晋升、职业困惑、职场瓶颈
- 话题:客户端、h5、b端、低代码、可视化、性能调优、监控告警、国际化、研发效率、职场沟通、向上管理、团队管理等;
- 方式:语音;
- 沟通步骤(你将获得的服务):
- 现状背景描述
- 困惑和诉求沟通
- 解决方案探讨和指导
- 方向指引、技巧传授、抉择建议等开放性建议
- 系统性总结和根据个人特点、诉求得出的发展建议
欢迎关注我的公众号“技术园游会”,原创技术文章第一时间推送。
引言
前端比较棘手的H5的实践,H5作为一种天然具备跨端属性的网页方案,在交付里被作为低成本的前端手段,但是如果不做专门的优化,H5方案的性能会逐渐被业务的发展给拖慢。这就使H5 Hybrid的性能优化成为了前端人一种挑战。
本文旨在全面探讨Hybrid混合开发的性能优化实践,从加载到运行、渲染,再到与原生通信的优化,以及针对FCP、FMP、LCP、CLS、TTI和秒开率的具体优化策略,最后介绍离线包、接口预加载和SSR等方案策略,通过实践代码和真实案例,为开发者提供一套系统性的优化实践参考,姑且抛砖引玉吧。
请君上座。
I 按阶段优化
1. 加载阶段优化
目标:减少启动时间和等待时间,让用户快点看到内容,知道网页没有卡死,提升用户的首屏体验。
策略:这部分的处理其实比较纯粹,就是用好缓存,记住策略要点:内置好但占体积,预加载选好时机,瘦身比较费劲。
资源压缩与CDN加速
资源压缩和cdn加速是最初级的手段,也是可以通过系统打包工具、nginx配置或者网络优化搞定的,研发成本低,效果明显。但是没啥技术含量,如果你想秀技术,最好不好在汇报中总提这个点。
-
如何使用:通过将JavaScript和CSS文件压缩,可以使网页的加载时间减少,用户留存率显著提升。
// webpack.config.js module.exports = { optimization: { minimize: true, minimizer: [new UglifyJsPlugin()], }, };
动态导入与路由懒加载
说到底是一种按需加载资源的手段,大白话说就是:那谁,没事别出来,我用你的时候找你。 这样能够减少非必要资源占用首屏资源的总体积或者包大小。
-
如何使用:通过路由懒加载,首屏加载时间可以大大减少,秒开率提升。
const Foo = () => import('./Foo.vue'); const routes = [ { path: '/foo', component: Foo }, ];
2. 运行阶段优化
目标:提升应用运行时的响应速度和流畅度。
策略:这个阶段的优化比较考验对浏览器渲染引擎、包加载机制、异步机制等核心前端概念的理解和应用。记住策略要点:抓到放小,能异步就异步,能合并就合并;
长任务识别与拆分
- 工具推荐:使用Chrome Performance Tab识别长任务,通过火焰图分析,识别出优化或拆分任务以减少阻塞。
Web Workers
大白话就是:你挺重要,但是你是幕后大佬,前台主角他们先上,等你准备好了随时叫我们~
-
如何应用:通过将复杂计算移到Worker中执行,UI响应延迟可以降低。自己找个场景试一下就明白了。
// worker.js self.onmessage = (e) => { const result = heavyComputation(e.data); self.postMessage(result); }; // main.js const worker = new Worker('worker.js'); worker.postMessage(someData); worker.onmessage = (e) => { // 使用计算结果 };
3. 渲染阶段优化
目标:前两个阶段都已经完毕了,万事俱备,只欠东风了。提高渲染效率,减少页面闪烁和重绘。
策略:浏览器的实现机制有很多,但大多数都是层级的。策略要点:能抱团就抱团,别独立;直接操作渲染树
CSS动画替代JavaScript动画
-
如何应用:通过替换JavaScript动画为CSS动画,可以实现更加平滑的动画效果,同时降低CPU使用率。
.element { transition: transform 0.5s ease-out; } .animate { transform: translateY(-100px); }
4. 与原生通信优化
目标:减少通信延迟,提升混合应用的原生体验。
策略:原生可以直接与操作系统通信,大白话描述:站在巨人的肩膀上,我也可以飞。
使用原生网络库
首屏请求使用原生网络库发起,在页面加载前就发起,最好在加载app首页时就发起,缓存到app里,页面加载时,拦截页面请求,直接返回数据给页面的网络库。
- 如何应用:请求能早发,等需要时候,取现成的。
批量处理消息
与原生app通信,也是IO,所以避免频繁调用桥方法。
-
如何应用:通过批量处理请求,减少与原生模块的通信频次,提升响应速度。
let messageQueue = []; let isSending = false; function sendMessageToNative(message) { messageQueue.push(message); if (!isSending) { isSending = true; cordova.exec(null, null, "MyPlugin", "processQueue", [messageQueue]); messageQueue = []; isSending = false; } }
II 性能指标监控与优化
线上采集:对于线上真实体验数据,就需要页面接入采集SDK,按照指标定义,通过performance的api进行打点,采集到每次访问的指标数据。
评估优化:为了准确监控LCP和FCP,可以使用Chrome DevTools中的Performance面板进行研发维度的模拟和分析。根据测试结果,不断调整和优化策略,直至达到理想的性能指标。
LCP( Largest Contentful Paint,最大内容渲染时间)
LCP是衡量页面主要内容(如大图片、文本块)渲染完成时间的关键指标,直接影响用户感知的页面加载速度。
优化策略:
1. 资源优化:这部分基本是上文描述的资源加载阶段的优化措施,达到的效果是,前置依赖资源提前加载、尽快加载完毕,开启渲染首元素。此处不赘述。
2. CSS Critical Path优化:确保首屏所需CSS尽快加载,可以提取首屏CSS为内联样式,避免阻塞渲染。
3. 图片优化:
- 使用现代图片格式,如WebP或AVIF,以减小文件大小。
- 实施懒加载策略,仅在图片即将进入视口时加载。
- 图片尺寸适当,避免不必要的放大或缩小,影响渲染速度。
实例代码:
<!-- 使用WebP格式 -->
<picture>
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Example">
</picture>
<!-- 懒加载示例 -->
<img src="placeholder.jpg" data-src="actual-image.jpg" class="lazy" onload="this.onload=null;this.classList.remove('lazy');">
<script>
document.addEventListener('DOMContentLoaded', function() {
var lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
if ('IntersectionObserver' in window) {
let observer = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
observer.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
observer.observe(lazyImage);
});
} else {
// Fallback for browsers without IntersectionObserver support
lazyImages.forEach(function(img) {
img.src = img.dataset.src;
});
}
});
</script>
FCP(First Contentful Paint,首次内容渲染时间)
FCP反映了用户看到页面上第一个内容(文本、图片、SVG等)的时间点,对用户体验至关重要。
优化实践:
1. 首屏渲染内容优化:如果你的页面,没有loading元素效果,那就加一个,是成本最小,FCP提升最有效的手段。 或者利用骨架屏、占位符等,在页面主要内容加载前展示占位元素,提高用户感知的加载速度。
2. 预加载:通过标签预加载关键CSS和字体。
3. 资源优化:与LCP一样,上文描述的资源加载阶段的优化措施也可以提升FCP。
4. 减少JavaScript阻塞:
- 使用
async或defer属性加载非关键JavaScript,确保它们不会阻塞页面渲染。 - 分析并移除或延迟加载非必要的JavaScript。
<!-- 使用async加载脚本 -->
<script async src="script.js"></script>
<!-- 使用defer加载脚本 -->
<script defer src="script.js"></script>
5. 高阶手段:确保服务器端渲染(SSR)或服务器推送(Server Push)关键资源,加速首屏内容呈现。这部分在下文单独展开描述。
CLS(Cumulative Layout Shift)
CLS的治理目标就一个:不要有页面撑开或者闪移的、让用户能感知到的视觉问题。所以,这个部分的治理就需要按照具体页面的结构和元素特点来进行分析,然后选出有效策略。
1. 尺寸固定的图片和元素:为图片和广告等动态内容预留固定空间,避免布局偏移。 2.占位:在真实实践中,可以用占位元素提前锁定空间,之后更换元素内容。
TTI(Time to Interactive)
1. 异步加载非关键脚本:使用async或defer属性加载脚本,确保页面尽早可交互。
2. 复杂性:这个指标的措施与前几个都有关联,所以前几个做好了,TTI的数据也就会自动变好,当然,还有其他影响因素在里面,比如核心元素要提起展示渲染,接口要提前加载。
通过这些具体的实践措施和持续的性能监控,能够显著提升Hybrid应用的LCP和FCP指标,从而为用户提供更加快速、流畅的浏览体验。
III 使用缓存策略优化
如果我们是在app内做H5的Hybird混合开发,就可以使用更加多维度的策略,主要是利用原生能力来做一些缓存机制,从而达到提前加载资源、提前访问数据、提前渲染页面的目的。 真实事件中,用的最多的就是离线包策略、接口预加载以及Server-Side Rendering(SSR) 。以下分别描述实践经验。
离线包
所有的缓存、离线都会这几两个核心概念:资源包获取时机、缓存的更新策略和缓存优先级,这三个都需要在实践中做反复打磨,才能在性能收益和资源成本上取得一个平衡。
1. 生成与更新离线包:使用Service Worker结合Cache API或离线存储技术(如IndexedDB),在用户首次访问时缓存关键资源,包括HTML、CSS、JavaScript以及必要图片等。
// sw.js (基于Service Worker)
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('my-cache-v1').then((cache) => {
return cache.addAll([
'./index.html',
'./styles.css',
'./scripts/main.js',
'./images/logo.png'
]);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
2. 更新策略:实现离线包的智能更新机制,检查更新并在后台静默下载新资源,确保下次访问时可用。
接口预加载
接口预加载可以在页面加载之前,把数据请求时机前置,比如可以app启动的时候、页面入口触发的时候使用原生网络库发起请求,缓存到app中,在页面加载的时候,策略层拦截请求,直接返回缓存的接口数据给接口响应,减少等待时间,提升交互响应速度。
1. 合理控制预加载量:避免过度预加载占用带宽和资源,需根据设备性能、网络状况和用户习惯动态调整。
Server-Side Rendering (SSR) 优化
SSR有助于解决首次加载速度慢的问题,因为它可以在服务器端直接生成完整的HTML页面,尤其是首屏页面,减少客户端渲染负担。比如,非SSR的架构方案,客户端要先发起首屏接口,拿到数据后在浏览器使用react运行时,把数据和react元素进行整合解析,最终才能渲染出HTML,给浏览器画出内容,但是SSR的这部分工作都交给Node去做了,客户端直接发起html请求,拿到的也是html数据,而不是之后接口数据。
实践代码(Vue.js + SSR示例):
// server.js
const { createServer } = require('http');
const { renderToString } = require('@vue/server-renderer');
const Vue = require('vue');
const App = require('./App.vue');
createServer((req, res) => {
const app = new Vue(App);
renderToString(app, (err, html) => {
if (err) {
res.statusCode = 500;
res.end('Internal Server Error');
return;
}
res.setHeader('Content-Type', 'text/html');
res.end(`
<!DOCTYPE html>
<html>
<head>
<title>Vue SSR Demo</title>
</head>
<body>
<div id="app">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
}).listen(3000);
数据预取:在服务器端渲染时,提前获取并注入页面所需的数据,减少客户端的额外请求。
通过上述各阶段的性能优化实践,结合针对具体性能指标的精细调优,以及离线包策略、接口预加载和SSR等高级方案的应用,结合我多年的时间经验,给大家做了个简单的展示。但性能优化永远是跟具体业务相关的,希望大家各取所需,做性能优化也能做到可监控、可衡量、可汇报。
欢迎关注我的公众号“技术园游会”,原创技术文章第一时间推送。
前端大师课往期精选