关于前端性能优化

面试中,面试官经常问关于前端性能优化的问题,前端性能优化已成为打造卓越用户体验的关键要素。一个响应迅速、加载流畅的网页,能够极大地提升用户满意度,增加用户留存率。

渲染层面的性能优化

重绘与重排:理解渲染的底层逻辑

重绘和重排是渲染过程中的两个关键概念。重绘指的是元素外观的改变,如背景颜色、文字颜色的调整,但不涉及布局的变动。由于不影响布局,重绘的代价相对较低。而重排则是指元素布局的改变,包括位置、大小的调整,以及元素的隐藏或显示等。一旦发生重排,浏览器需要重新计算布局,这可能会影响到其他元素的位置和大小。需要注意的是,重排必定会引发重绘,而重绘不一定会导致重排。

以下代码示例展示了如何通过批量修改 DOM 来减少重排和重绘的次数:

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
</head>

<body>
  <div id="myDiv">Hello, World!</div>
  <script>
    const div = document.getElementById('myDiv');
    // 欠佳做法:多次修改 DOM 会触发多次重排
    // div.style.color = 'red';
    // div.style.fontSize = '20px';
    // div.style.padding = '10px';

    // 优化做法:批量修改 DOM
    div.style.cssText = 'color: red; font-size: 20px; padding: 10px;';
  </script>
</body>

</html>

文档碎片:高效构建 DOM 的秘密武器

文档碎片是一种轻量级的 DOM 容器,它允许我们在内存中构建 DOM 结构,最后一次性将其插入到文档中。这样做可以显著减少重排和重绘的次数,提高渲染性能。

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
</head>

<body>
  <ul id="myList"></ul>
  <script>
    const list = document.getElementById('myList');
    const fragment = document.createDocumentFragment();
    for (let i = 0; i < 10; i++) {
      const li = document.createElement('li');
      li.textContent = `Item ${i}`;
      fragment.appendChild(li);
    }
    list.appendChild(fragment);
  </script>
</body>

</html>

资源加载优化

图片懒加载:节省首屏加载资源的良方

图片懒加载是一种有效的资源加载优化策略,它可以避免在首屏加载时一次性加载所有图片,从而减少资源消耗,提高页面加载速度。我们可以使用 getBoundingClientRect() 方法或 IntersectionObserver API 来实现图片懒加载。

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
</head>

<body>
  <img class="lazy" data-src="https://picsum.photos/200/300" alt="Lazy Image">
  <script>
    const lazyImages = document.querySelectorAll('.lazy');
    const observer = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          img.classList.remove('lazy');
          observer.unobserve(img);
        }
      });
    });
    lazyImages.forEach(img => {
      observer.observe(img);
    });
  </script>
</body>

</html>

路由懒加载:按需加载代码,提升首屏速度

在单页面应用中,路由懒加载是一项重要的优化技术。它可以将不同路由的代码分割成独立的文件,只有当用户访问该路由时,才会加载相应的代码。这样可以显著减少首屏加载时间,提高用户体验。

javascript

javascript 复制代码
// Vue 路由懒加载示例
const routes = [
  {
    path: '/about',
    component: () => import('./views/About.vue')
  }
];

资源预加载与 DNS 预解析:提前布局,加速资源加载

使用 <link rel="preload"> 可以预加载重要的资源,而 <link rel="dns-prefetch"> 则可以预解析 DNS,提前建立与服务器的连接,从而提高资源加载速度。

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <link rel="preload" href="styles.css" as="style">
  <link rel="dns-prefetch" href="//example.com">
</head>

<body>
  <!-- 页面内容 -->
</body>

</html>

JS 执行优化

防抖与节流:控制函数执行频率的利器

防抖和节流是两种常用的优化技术,它们可以限制函数的执行频率,减少不必要的计算和资源消耗。在处理高频事件(如滚动、窗口大小改变等)时,这两种技术尤为有用。

ini 复制代码
// 防抖函数
function debounce(func, delay) {
  let timer;
  return function () {
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

// 节流函数
function throttle(func, limit) {
  let inThrottle;
  return function () {
    const context = this;
    const args = arguments;
    if (!inThrottle) {
      func.apply(context, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

Web Worker:释放主线程,提升计算性能

Web Worker 是 HTML5 的一项重要特性,它允许我们在后台线程中执行 JavaScript 代码,从而避免阻塞主线程。这对于处理复杂的计算任务非常有用,可以显著提升页面的响应性能。

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
</head>

<body>
  <button id="startWorker">Start Worker</button>
  <script>
    const startWorkerButton = document.getElementById('startWorker');
    startWorkerButton.addEventListener('click', () => {
      if (typeof Worker !== 'undefined') {
        const worker = new Worker('worker.js');
        worker.onmessage = function (event) {
          console.log('Received message from worker:', event.data);
        };
        worker.postMessage('Start calculation');
      } else {
        console.log('Web Workers are not supported in this browser.');
      }
    });
  </script>
</body>

</html>
ini 复制代码
// worker.js
onmessage = function (event) {
  if (event.data === 'Start calculation') {
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += i;
    }
    postMessage(result);
  }
};

框架层面优化

React 的 useMemouseCallback:缓存计算结果,避免重复渲染

在 React 中,useMemouseCallback 是两个非常实用的钩子函数。useMemo 可以缓存计算结果,避免在每次渲染时都进行重复计算;useCallback 则可以缓存函数,避免在每次渲染时都创建新的函数实例。

javascript 复制代码
import React, { useMemo, useCallback } from 'react';

function App() {
  const [count, setCount] = React.useState(0);

  const expensiveCalculation = useMemo(() => {
    let sum = 0;
    for (let i = 0; i < 1000000; i++) {
      sum += i;
    }
    return sum;
  }, []);

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Expensive Calculation: {expensiveCalculation}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default App;

使用 key 优化列表渲染:帮助框架高效识别元素变化

在 React 或 Vue 中,使用 key 可以帮助框架快速识别哪些元素发生了变化,从而优化列表的渲染性能。key 应该是唯一的,并且尽量保持稳定。

javascript 复制代码
import React from 'react';

function List() {
  const items = [1, 2, 3, 4, 5];
  return (
    <ul>
      {items.map(item => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
}

export default List;

缓存策略

本地存储与会话存储:浏览器中的数据缓存方案

localStoragesessionStorage 是浏览器提供的两种本地存储机制,它们可以用于在浏览器中存储数据,避免重复请求服务器。localStorage 中的数据会一直保留,直到手动清除;而 sessionStorage 中的数据会在会话结束时自动清除。

javascript 复制代码
// 存储数据
localStorage.setItem('username', 'JohnDoe');

// 获取数据
const username = localStorage.getItem('username');
console.log(username);

强缓存与协商缓存:减轻服务器压力,加速页面加载

通过设置 HTTP 响应头,我们可以实现强缓存和协商缓存。强缓存可以让浏览器直接使用本地缓存的资源,而无需向服务器发送请求;协商缓存则需要向服务器发送一个请求,询问服务器该资源是否有更新,如果没有更新,则使用本地缓存。

ini 复制代码
// Node.js 示例
const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  const filePath = path.join(__dirname, 'public', req.url);
  fs.stat(filePath, (err, stats) => {
    if (err) {
      res.statusCode = 404;
      res.end('File not found');
    } else {
      const lastModified = new Date(stats.mtime).toUTCString();
      const ifModifiedSince = req.headers['if-modified-since'];
      if (ifModifiedSince === lastModified) {
        res.statusCode = 304;
        res.end();
      } else {
        res.setHeader('Last-Modified', lastModified);
        res.setHeader('Cache-Control', 'max-age=3600'); // 强缓存 1 小时
        fs.createReadStream(filePath).pipe(res);
      }
    }
  });
});

server.listen(3000, () => {
  console.log('Server is running on port 3000');
});

网络优化

CDN 加速:借助分布式网络,提升资源加载速度

CDN(内容分发网络)可以将静态资源分发到离用户最近的节点,从而减少网络延迟,提高资源加载速度。使用 CDN 可以显著提升页面的响应性能,尤其是对于全球范围内的用户。

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
</head>

<body>
  <!-- 页面内容 -->
</body>

</html>

Gzip 压缩:减少传输数据大小,提高网络效率

在服务器端开启 Gzip 压缩可以将传输的数据进行压缩,从而减少数据的大小,提高网络传输效率。这对于提升页面加载速度和节省带宽都非常有帮助。

ini 复制代码
// Node.js 示例
const express = require('express');
const compression = require('compression');
const app = express();

app.use(compression());

app.get('/', (req, res) => {
  res.send('Hello, World!');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

HTTP/2 多路复用:高效传输数据,提升大文件处理能力

HTTP/2 的多路复用特性允许在一个连接上同时传输多个请求和响应,这大大提高了大文件上传和下载的速度。与 HTTP/1.1 相比,HTTP/2 可以更高效地利用网络资源。

javascript 复制代码
// Node.js 示例
const http2 = require('http2');
const fs = require('fs');

const server = http2.createSecureServer({
  key: fs.readFileSync('server.key'),
  cert: fs.readFileSync('server.crt')
});

server.on('stream', (stream, headers) => {
  stream.respond({
    'content-type': 'text/html',
    ':status': 200
  });
  stream.end('<h1>Hello, HTTP/2!</h1>');
});

server.listen(8443, () => {
  console.log('Server is running on port 8443');
});

首屏优化

SSR(服务器端渲染):提前生成 HTML,加速首屏显示

SSR 可以在服务器端生成 HTML 内容,然后将其发送给浏览器。这样可以减少首屏加载时间,提高搜索引擎优化(SEO)效果。对于需要快速显示内容的页面,SSR 是一个非常有效的优化方案。

ini 复制代码
// Node.js + React SSR 示例
const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const App = require('./App');

const app = express();

app.get('/', (req, res) => {
  const html = ReactDOMServer.renderToString(<App />);
  const page = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>SSR Example</title>
    </head>
    <body>
      <div id="root">${html}</div>
      <script src="client.js"></script>
    </body>
    </html>
  `;
  res.send(page);
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

骨架屏:营造快速加载的视觉体验

骨架屏是一种在页面加载时显示的占位布局,它可以让用户感觉页面正在快速加载,从而提高用户体验。骨架屏通常使用简单的图形和动画来模拟页面的结构。

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <style>
    .skeleton {
      background-color: #f0f0f0;
      animation: skeleton-loading 1s infinite alternate;
    }

    @keyframes skeleton-loading {
      0% {
        opacity: 0.6;
      }

      100% {
        opacity: 1;
      }
    }
  </style>
</head>

<body>
  <div class="skeleton" style="width: 200px; height: 20px;"></div>
  <div class="skeleton" style="width: 150px; height: 20px; margin-top: 10px;"></div>
  <script>
    // 模拟数据加载
    setTimeout(() => {
      const skeletons = document.querySelectorAll('.skeleton');
      skeletons.forEach(skeleton => {
        skeleton.style.display = 'none';
      });
      // 显示真实内容
    }, 2000);
  </script>
</body>

</html>

首屏数据预加载:提前推送数据,减少用户等待

使用 HTTP/2 的 Server Push 可以在服务器端主动推送首屏所需的数据,这样可以减少用户的等待时间,提高页面的响应速度。

javascript 复制代码
// Node.js 示例
const http2 = require('http2');
const fs = require('fs');

const server = http2.createSecureServer({
  key: fs.readFileSync('server.key'),
  cert: fs.readFileSync('server.crt')
});

server.on('stream', (stream, headers) => {
  stream.pushStream({ ':path': '/styles.css' }, (pushStream) => {
    pushStream.respondWithFile('styles.css', {
      'content-type': 'text/css'
    });
  });
  stream.respond({
    'content-type': 'text/html',
    ':status': 200
  });
  stream.end('<html><head><link rel="stylesheet" href="styles.css"></head><body><h1>Hello, HTTP/2!</h1></body></html>');
});

server.listen(8443, () => {
  console.log('Server is running on port 8443');
});

监控与分析

Performance API:精准测量页面性能指标

使用 Performance API 可以测量页面的各种性能指标,如加载时间、渲染时间等。通过分析这些指标,我们可以找出页面性能的瓶颈,从而进行针对性的优化。

javascript 复制代码
window.addEventListener('load', () => {
  const performanceData = window.performance.timing;
  const loadTime = performanceData.loadEventEnd - performanceData.navigationStart;
  console.log(`Page loaded in ${loadTime} milliseconds`);
});

如果以上性能关于性能优化对于你有帮助的话,请给作者点个赞吧

相关推荐
斯~内克4 小时前
Electron 菜单系统深度解析:从基础到高级实践
前端·javascript·electron
数据知道4 小时前
【YAML】一文掌握 YAML 的详细用法(YAML 备忘速查)
前端·yaml
dr李四维4 小时前
vue生命周期、钩子以及跨域问题简介
前端·javascript·vue.js·websocket·跨域问题·vue生命周期·钩子函数
旭久4 小时前
react+antd中做一个外部按钮新增 表格内部本地新增一条数据并且支持编辑删除(无难度上手)
前端·javascript·react.js
windyrain4 小时前
ant design pro 模版简化工具
前端·react.js·ant design
浪遏4 小时前
我的远程实习(六) | 一个demo讲清Auth.js国外平台登录鉴权👈|nextjs
前端·面试·next.js
GISer_Jing5 小时前
React-Markdown详解
前端·react.js·前端框架
太阳花ˉ5 小时前
React(九)React Hooks
前端·react.js
拉不动的猪6 小时前
vue与react的简单问答
前端·javascript·面试