大厂白话版360°全方位前端性能优化

692 阅读12分钟

大师课:前端,我眼看它出现、它成长、它成熟,却远远不是结束。 因此,我想说给你听,让你对我们身处的行业,有个大框架式的认知,知道:它是怎么来的,从而去判断,它要往哪里去。

前言:作为一位拥有13年经验的资深前端开发者,我深知性能优化在实践上的难点和痛点。一方面,业务需要更好的打开率、更高的转化率,一方面,扑面而来的优化手段却又千篇一律,好像知道很多,但又不知道怎么着手拿效果。

问前端 13年大厂前端资深大佬在线回答问题,我不唬人,是真心为你们服务,想传帮带,把大厂的经验和思维方式通过这种方式告诉你们

1对1量身制定个人发展方案,提供业界专业建议,和面试指导。已帮助服务过上百名毕业生;

  • 适合人群:应届毕业生找方向、前端小白打怪晋升、职业困惑、职场瓶颈
  • 话题:客户端、h5、b端、低代码、可视化、性能调优、监控告警、国际化、研发效率、职场沟通、向上管理、团队管理等;
  • 方式:语音;
  • 沟通步骤(你将获得的服务):
  • 现状背景描述
  • 困惑和诉求沟通
  • 解决方案探讨和指导
  • 方向指引、技巧传授、抉择建议等开放性建议
  • 系统性总结和根据个人特点、诉求得出的发展建议

欢迎关注我的公众号“技术园游会”,原创技术文章第一时间推送。

WX20240523-111603@2x.png

引言

前端比较棘手的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 {
      transformtranslateY(-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阻塞

  • 使用asyncdefer属性加载非关键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. 异步加载非关键脚本:使用asyncdefer属性加载脚本,确保页面尽早可交互。

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等高级方案的应用,结合我多年的时间经验,给大家做了个简单的展示。但性能优化永远是跟具体业务相关的,希望大家各取所需,做性能优化也能做到可监控、可衡量、可汇报

欢迎关注我的公众号“技术园游会”,原创技术文章第一时间推送。

前端大师课往期精选