前端优化相关问题

1,369 阅读12分钟

1. 页面访问、渲染速度从哪几个方向优化?

  1. 异步加载路由(按需加载、懒加载)
  2. 异步加载组件、插件(模块按功能做最小化拆分便于异步加载和复用)
  3. 延迟加载 <Image/> 资源
  4. 尽量减少 DOM 访问和操作,减少重绘和回流
  5. 尽量使用防抖 debounce 和节流 Throttle
  6. 资源压缩,css js img 压缩体积
  7. 启用缓存,对不常变动的静态资源进行缓存,强缓存 catch-control,协商缓存 etag
  8. SSR / SSG / 预渲染 prerander-spa-plugin
  9. DNS 解析慢,开启 DNS 预解析。<link rel="dns-prefetch" href="//example.com">
  10. 插件可以使用 CDN 引入,减少打包体积,提高访问速度
  11. webpack配置优化
    1. 区分dev和prod配置,避免prod引用无效得插件
    2. 使用SplitChunksPlugin,cacheGroups配置缓存组,提升打包速度
    3. 自动拆分chunks,抽离公用代码和第三方插件
      • node_modules内得插件单独拆到Vendors.chunk,新的chunk可以被共享
      • 自己写的 utils 工具函数根据引用次数minChunks配置,拆到单独chunk内
      • enforceSizeThreshold 设置拆分的体积阈值,超过阈值拆到单独chunk内
      • minSize 生成 chunk 的最小体积,避免生成过小的chunk文件
    4. extract-text-webpack-plugin,把包含CSS的JS文件提取单独CSS文件,支持CSS和SourceMaps按需加载,异步加载,没有重复的编译(性能)
    5. mini-css-extract-plugin,抽离css及css压缩配置
    6. terser-webpack-plugin,js压缩
    7. image-minimizer-webpack-plugin,图片压缩(可选无损压缩和有损压缩)
    8. webpack@4xx (url-loader) ;webpack@5xx,type:"asset"配置parser.dataUrlCondition.maxSize阀值,超出阀值,自动转为data-url,base64格式。
    9. prerander-spa-plugin,配置页面预渲染,设置单独entry出口文件,提升某个页面的到达时间,利于SEO
    10. 过滤注释,过滤console.log

2. Image 加载的性能优化

  1. 图片压缩:压缩图片体积
  2. 图片格式转化:转为base64格式
  3. 使用矢量图:svg、webP格式
  4. 减少图片请求:使用雪碧图,减少请求次数
  5. CDN加速:使用CDN服务加速资源加载
  6. 延迟加载:只加载在视图内的图片,img 设置loading="lazy"属性
    // 设置 loading="lazy" 属性, 在视图内才会加载。兼容问题需要用第三方处理
    // srcset 根据不同的屏幕分辨率或设备像素比(DPR)来提供不同尺寸的图片资源
    <img src="懒加载图片,默认图.jpg"
         srcset="小图.jpgg 300w, 默认图.jpg 600w, 大图.jpg 1000w"
         sizes="(max-width: 600px) 100vw, 50vw"
         alt="懒加载图片。支持响应式"
         loading="lazy"
    />
    
  7. 先加载缩略图,再加载高清图
    <image src="缩略图.url" data-src="高清图.url" loading="lazy" class="lazy-load" />
    
    <script>
    document.addEventListener("DOMContentLoaded", function() {
        // 拿到所有需要高清图需要懒加载的img
        var lazyImages = [].slice.call(document.querySelectorAll("img.lazy-load"));
        if ("IntersectionObserver" in window) {
            // 当图片处于视图窗口内时去处理高清图加载
            let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
                entries.forEach(function(entry) {
                    if (entry.isIntersecting) {
                        let lazyImage = entry.target;
                        lazyImage.src = lazyImage.dataset.src;
                        lazyImage.classList.remove("lazy-load");
                        lazyImageObserver.unobserve(lazyImage);
                    }
                });
            });
            lazyImages.forEach(function(lazyImage) {
                lazyImageObserver.observe(lazyImage);
            });
        } else {
            // 浏览器若不支持IntersectionObserver,则用源src
            lazyImages.forEach(function(lazyImage) {
                lazyImage.src = lazyImage.dataset.src;
            });
        }
        // JavaScript中为加载完成的图片添加`loaded`类
        lazyImage.onload = function() {
            lazyImage.classList.add('loaded');
        };
    });
    </script>
    // 通过CSS技巧来进一步优化体验,为图片添加平滑过渡效果
    <style>
        img.lazy-load { transition: opacity 0.3s ease-in-out; }
        img { opacity: 0; will-change: opacity; }
        img.loaded {  opacity: 1; }
    </style>
    

3. Web 应用中css js img 加载失败处理

1. JS 加载失败的处理

<script src="js文件.js" onerror="function() { loadScript('备用脚本文件.js') }"></script>
<script>
    function loadScript (url) {
      const jsflie = document.createElement('script');
      jsflie.src = url; // 备用js文件url
      document.head.appendChild(jsflie);
    };
</script>

2. img 加载失败的处理

<img src="图片地址.jpg" 
     onerror="this.onerror=null; this.src='备用图片地址.jpg / 默认图片地.jpg';"
     alt="图片加载失败提示语"
>

3. CSS 加载失败的处理

  • 动态加载css <link> 标签不支持 onerror,通过JS动态创建 <link> 时,可以设置 onerror 事件监听。
    const loadCss = (url, errorFn) => {
        var link = document.createElement('link');
        link.type = 'text/css';
        link.rel = 'stylesheet';
        link.href = url;
        // 尝试捕捉加载错误
        link.onerror = function() {
            errorFn && errorFn();
        };
        document.head.appendChild(link);
    }
    loadCss('/css地址.css', () => { loadCss('/css备用地址.css') })
    
  • 备用样式
    <!-- 默认基础样式 -->
    <style>
    body { font-family: Arial, sans-serif; color: #333; background-color: #fff; }
    </style>
    <!-- 主样式表 -->
    <link rel="stylesheet" href="styles.css">
    

4. SEO 优化如何处理?

SEO(Search Engine Optimization,搜索引擎优化)是提升网站在搜索引擎中排名、增加自然流量。

  1. SSR 技术层面优化
  2. 服务端渲染 SSR(如 Next.js / Nuxt.js)
  3. 预渲染(Prerendering) SPA
  4. SSG
  5. 准确的TDK使用:title description keywords
    <title>SEO优化指南</title>
    <meta name="description">
    <meta name="keywords" content="SEO优化, 关键词优化, 网站推广, 搜索引擎优化">
    
  6. 合理使用语语义化标签
    Header / Nav / ASide / Footer / H1 / H2 便于搜索引擎识别,如:
    <ASide>侧导航</ASide>
    <H1>SEO标题</H1>
    <img src="seo-guide.jpg" alt="SEO优化步骤示意图">
    
  7. 链接优化
    内部链接使用有意义的锚文本,如:
    <a href="/blog/seo-basics">SEO基础知识</a>
    
    外部链接建议使用 `rel="nofollow"`(如果不想传递权重)
    <a href="https://example.com" rel="nofollow">第三方资源</a>
    
  8. 设置robots.txt文件
    • 指引搜索引擎更高效地抓取网站内容
    • 避免不希望被收录的内容(如后台、测试页面)
    • 控制不同搜索引擎的行为(Google、Bing、百度等)
  9. 响应式的代码也会提升SEO权重

5. 大文件上传方案,切片上传

  1. 大文件上传时常见问题
    • 上传失败率高(网络中断、超时)
    • 占用大量内存和带宽
    • 无法断点续传
  2. 切片上传方案流程
    1. 选择文件,计算文件 Hash(生成MD5UUID用于唯一标识文件或断点续传)
    2. 使用 File.slice()将文件切片(每个Chunk大小5MB),每个chunk生成唯一标识
    3. 并发上传切片,比如设置并发数量为5个
    4. 记录上传状态,使用 localStorage 记录哪些切片已上传
    5. 上传完成,通知后端合并文件
  3. 功能优势
    • 断点续传 : 刷新页面后继续传未上传的切片
    • 秒传 : 上传前先校验文件 Hash,已存在就跳过
    • 进度条 : 显示已上传切片数量 / 总切片数
    • 自动重试 : 切片失败后自动重试几次
    • Hash 校验 : 上传前计算文件 Hash,用于校验完整性

6. CSS 移动端 1 像素问题

  1. 补充像素知识

    • 物理像素(设备像素):移动设备出厂时,不同设备自带的不同像素,也称硬件像素;
    • 逻辑像素(CSS像素):即css中记录的像素。
    • 物理像素和逻辑像素存在比例关系,比例多少与设备相关。
    • js 中 window.devicePixelRatio 来获取比例。
    • css 中用媒体查询的 -webkit-min-device-pixel-ratio 来获取比例。
  2. 出现问题的原因:

    • 在部分手机上border=1px无法达到我们想要的效果。这是因为 devicePixelRatio 特性导致,iPhone 的 devicePixelRatio==2 。
  3. 解决方案:transform: scale(0.5) + 媒体查询 (推荐方案: 很灵活)

    // 设置 ::after 或 ::befor 实现边线 
    div::after{
       content: '';
       width: 100%;
       border-bottom: 1px solid #000;
       transform: scaleY(0.5);
    }
    /* 2倍屏 */
    @media only screen and (-webkit-min-device-pixel-ratio: 2.0) {
       .border-bottom::after {
           -webkit-transform: scaleY(0.5);
           transform: scaleY(0.5);
       }
    }
    /* 3倍屏 */
    @media only screen and (-webkit-min-device-pixel-ratio: 3.0) {
       .border-bottom::after {
           -webkit-transform: scaleY(0.33);
           transform: scaleY(0.33);
       }
    }
    

7. Number 超过 JS 最大值如何处理?

  1. 问题产生背景
    • 最大的有限值(最大可表示小数) Number.MAX_VALUE≈1.7976931348623157e+308
    • 超过这个值的数字会被自动转换为 Infinity
  2. 解决方案
    1. BigInt 函数
      // **方式一:在整数字面量后加 `n`**
      const big = 123456789123456789123456789n
      // **方式二:使用 `BigInt()` 函数**
      const big2 = BigInt("9007199254740992");
      
    2. 第三方库:decimal.js / big.js / bignumber.js
    1. decimal.js 支持加减乘除、取模、幂运算、开方、三角函数、高精度浮点数、四则运算、科学计算、金融计算,支持 toJSON()toString() 等格式化输出。
    2. big.js 更轻量,功能稍少但性能更好, 适合只需要基本加减乘除的场景。
    3. bignumber.js 功能介于decimal.jsbig.js之间,支持数学函数,适合金融系统。
      const Decimal = require('decimal.js');
      let a = new Decimal(123.456789);
      let b = new Decimal('98765432109876543210987654321.123456789');
      let result = a.plus(b); // 加法
      console.log(result.toString()); // 输出高精度结果
      // ------------------------------------------------------------------------
      const Big = require('big.js');
      let x = new Big('123.456');
      let y = new Big('100');
      console.log(x.times(y).toString()); // 12345.6
      

8. 前端实现截图功能

  1. 使用HTML5 <canvas> 元素的绘图能力,可以将页面指定的 DOM 元素整个页面元素绘制到画布上,然后从画布中导出图像数据。
    • 优点
      1. 兼容性好,浏览器支持广泛;
      2. 能够灵活控制截图的内容和样式。
    • 缺点:对于复杂的 CSS 样式或包含视频、Canvas 动画等动态内容时,可能无法完全准确地捕捉。
  2. 第三方库:html2canvas,读取 DOM 节点及样式并在 <canvas> 上重建这些节点来生成图像。
    • 优点
      1. 易于使用,不需要深入了解底层技术
      2. 可以对指定的 DOM 元素或整个页面进行截图
      3. 支持设置 scale 参数调整输出图像的质量和尺寸,导出PNG或JPEG格式的图片
      4. 支持大多数 CSS 特性和布局方式
    • 缺点
      1. 可能存在与某些 CSS 属性或框架不兼容的情况;
      2. html2canvas 可能无法准确捕捉到 <video> 标签的内容或动态的 <canvas> 内容
      3. 不支持 :before:after 等伪类和伪元素
      4. 页面中不同源的资源,需要单独设置跨域访问( 跨域资源共享(CORS) 配置
      5. 性能可能不如直接操作 Canvas

9. 实现页面访问的进度条

  1. 方案一:不依赖框架和库
    • 使用 window.performance API 监控页面加载状态
    • 使用 window.onload 事件,判断页面是否加载完成
  2. 方案二useNavigation()+ 手写 css 进度条 (适用:React Router v6.4+)
    const navigation = useNavigation() // 它可以监听当前的导航状态
    navigation.state === "loading"  // 正在导航(页面切换中),显示进度条并开始动画
    navigation.state === "idle"  // 隐藏进度条或完成动画
    
  3. 方案三: VUE中使用 router前置守卫和后置守卫
    router.beforeEach((to, from, next) => { // 开始进度条动画 })
    router.afterEach((to, from, next) => { // 关闭进度条 })
    

10. 前端水印功能

  1. 方案一:利用 CSS 伪元素 ::before 或 ::after 在页面背景层添加水印。
    <div id="watermark"></div>
    <style>
        #watermark {
          position: fixed;
          width: 100%;
          height: 100%;
          z-index: 9999; /* 确保在其他内容之上 */
          pointer-events: none; /* 让水印不会影响用户的交互 */
          background: url(
          "data:image/svg+xml,
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" style="transform: rotate(-45deg); font-size: 20px;">
              <text x="10" y="50">水印内容文本</text>
            </svg>
          ") repeat;
    </style>
    
  2. 方案二: 利用 <canvas> 元素绘制水印,灵活的样式控制,动态生成水印内容
    <canvas id="watermarkCanvas" width="300" height="300"></canvas>
    <script>
    const createWatermark = (text, containerWidth, containerHeight) => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = containerWidth;
        canvas.height = containerHeight;
        ctx.rotate((-45 * Math.PI) / 180);
        ctx.font = '20pt Arial';
        ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
        ctx.textAlign = 'center';
        for (let x = -containerWidth; x < containerWidth; x += 100) {
            for (let y = -containerHeight; y < containerHeight; y += 100) {
                ctx.fillText(text, x, y);
            }
        }
        return canvas.toDataURL();
    }
    document.body.style.backgroundImage = `url(${createWatermark('水印内容', 500, 500)})`;
    </script>
    

11. 实现PC端 & H5端的适配

  1. 方案一:根据响应式使用不同的css规则:
    1. 响应式设计:通过 CSS 中的媒体查询来调整页面布局和样式适应不同的屏幕尺寸
    2. 弹性网格布局:基于百分比或flex/grid布局系统,布局可以根据屏幕大小自动调整
    3. 视口设置:设置视口元标签(<meta name="viewport" content="width=device-width, initial-scale=1">)。
  2. 方案二:两套代码,根据不同端跳转不同地址
    1. 判断当前访问设备是移动端还是PC端浏览器 User-Agent(UA) )。
    2. window.location.href = isMobile() ? "移动端" : "PC端";
    const isMobile = () => {
      return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
    }
    if (isMobile()) {
      window.location.href = "https://yourdomain.com/mobile"; // render(<MobileApp />);
    } else {
      window.location.href = "https://yourdomain.com/pc"; // render(<PcApp />);
    }
    

12. 如何修改 npm 包

  1. 方案一:使用 patch-package(推荐)
    • 步骤
      1. 安装:npm install patch-package --save-dev
      2. 修改 node_modules 中的代码
      3. 生成补丁:npx patch-package package-name
      4. 提交补丁文件到 Git
      5. 团队成员拉取代码后,自动应用补丁
    • 优点:修改可提交到版本控制;适合多人协作;不影响原包结构。
    • 缺点:只能做小范围修改,临时修改;不适合大规模重构。
  2. 方案二:本地修改 node_modules(临时修改)
    • 优点: 快速简单
    • 缺点: 一旦重新安装依赖,修改会丢失,不适合团队协作或生产环境
  3. 方案三:Fork 原始仓库 + 自己发布为私有包
    • 步骤
      1. 在 GitHub 上 Fork 原始 npm 包的仓库。
      2. 克隆到本地进行修改。
      3. 修改完成后,打 tag 并发布到自己的 npm 账号下(私有或公开)。
      4. 在项目中安装你自己的包。
    • 优点:完全掌控源码,可持续更新维护
    • 缺点:需要维护一个独立的包,后续同步原包更新较麻烦

13. 前端请求安全,加密

  1. 使用HTTPS,HTTPS通过SSL/TLS协议加密客户端与服务器之间的通信,防止中间人攻击(MITM),确保传输的数据不会被窃听或篡改
  2. 限流:对同一IP或User进行访问频率限制,增加验证码、图形验证等
  3. 数据加密(非对称加密,对称加密,MD5 SHA AES PGP)
  • 补充:SHA(Secure Hash Algorithm,安全哈希算法),快速,单向不可逆,微小变化就会改变哈希
    1. SHA-1:输出长度:160 位(20 字节)。已被认为不安全
    2. SHA-2
      • SHA-256:输出 256 位(32 字节)。非常流行,广泛应用于区块链(如比特币)、SSL/TLS、数字签名、密码存储(通常结合盐值)等。
      • SHA-384:输出 384 位(48 字节)。
      • SHA-512:输出 512 位(64 字节)。常用于需要更高安全强度的场景。

14. 接口请求大并发问题怎么处理?

  1. 请求队列,设置最大请求数量,按队列处理请求的发送
  2. 防抖/节流,合并不必要的请求,减少并发
  3. 分页加载,减少请求量,延迟加载不必要的数据