迈步

33 阅读53分钟

1、服务端渲染支持哪些生命周期,localstorage可以在什么时候写

问题一:服务端渲染支持哪些生命周期?

在服务端渲染中,只有 beforeCreate 和 created 这两个生命周期钩子会在服务器端执行

原因在于:
SSR 的本质是在 Node.js 服务器上生成应用的 HTML 字符串。这是一个无 DOM 环境

  • 不支持的生命周期:所有与 DOM 相关的钩子,如 onMountedonUpdatedonBeforeUnmount 以及 onMounted 内部的 ref.value.xxx 操作,在服务端都不会执行。因为此时根本没有浏览器环境,也没有真实的 DOM 元素可供挂载和操作。
  • 支持的生命周期beforeCreate 和 created 这两个钩子(在 Composition API 中,是 setup 函数本身)主要进行组件的数据初始化、状态设置、以及异步数据获取,不依赖于 DOM,因此可以在服务端运行。
详细解释和最佳实践:

1. 在服务端执行的钩子中你应该做什么?

  • 数据预取:这是 SSR 的核心价值之一。在 setup 或 created 中,你可以发起 API 请求,将获取到的数据填充到 Store(如 Pinia)或组件的数据中。这样,当 HTML 被发送到客户端时,已经包含了渲染好的数据。
  • 初始化非 DOM 相关的状态

2. 在服务端执行的钩子中你绝对不应该做什么?

  • 访问 DOM 或 windowdocument 等浏览器特有的对象。这会导致服务端报错。
  • 设置定时器或执行有副作用的操作,因为这些操作不会在客户端被清理,可能导致内存泄漏。

问题二:localStorage 可以在什么时候写?

简单直接的答案:localStorage 只能在客户端(浏览器)环境中写入和读取。

原因在于:  localStorage 是浏览器提供的一个 Web API,它是 window 对象的一个属性。在服务端渲染的 Node.js 环境中,window 对象是不存在的。

详细解释和正确时机:

1. 为什么不能在服务端写?

如果你在服务端生命周期(beforeCreatecreatedsetup)中尝试写入 localStorage,你会得到一个类似 ReferenceError: window is not defined 的错误,导致服务端渲染失败。

2. 正确的写入时机是什么?

你必须确保操作 localStorage 的代码仅在客户端执行。以下是几个安全的时机:

  • a. 在 onMounted 生命周期中
    这是最常见和最安全的方式。因为 onMounted 保证只在客户端执行。

服务端负责组装数据和初始 HTML,客户端负责挂载 DOM 和实现交互。任何与浏览器强相关的操作,都必须放到客户端生命周期中。

2、ts的type或者interface可以重复声明吗

  • interface:支持重复声明,自动合并(适合需要扩展已有接口的场景,如扩展第三方库的类型)。
  • type:不支持重复声明,重复定义会报错(适合定义一次性的复合类型、联合类型等)。

3、哪些元素可以开启3d加速

开启 3D 加速(通常指硬件加速)的本质是让 GPU 来参与页面元素的渲染,从而减轻 CPU 负担,提升动画和交互的流畅度。

浏览器会自动将某些 CSS 操作视为 3D 变换,从而触发硬件加速层。以下是能够开启 3D 加速的主要元素和 CSS 属性:


核心原理:触发 GPU 渲染层

浏览器在渲染一个元素时,会将其放入一个“渲染层”中。默认情况下,许多元素都在同一个层里。当对元素应用特定的 CSS 属性时,浏览器会将其提升到一个独立的复合层,这个层可以由 GPU 直接处理,从而实现硬件加速。

一、触发 3D 加速的核心 CSS 属性

浏览器会对设置了以下属性的元素启用 GPU 加速(创建独立的 “合成层”,减少重排重绘):

1. transform: translateZ(0) 及 3D 变换相关
  • 原理:任何涉及 3D 变换的 transform 属性(即使是 translateZ(0) 这种 “零变换”)会强制浏览器启用 3D 加速,将元素放入独立的合成层。
2. will-change: transform
  • 作用:提前告知浏览器元素可能发生的变换,让浏览器预先生成优化配置(如分配 GPU 资源),避免变换时的性能抖动。
3. perspective(父元素设置)
  • 作用:为子元素创建 3D 空间透视效果,父元素设置 perspective 后,子元素的 3D 变换会更真实(模拟人眼视角),同时触发 GPU 加速。

二、适合开启 3D 加速的元素场景

并非所有元素都需要开启 3D 加速,以下场景使用可显著提升性能:

  1. 高频动画元素

    • 如轮播图、滚动动画、悬浮效果(hover 时的位移 / 缩放)等,通过 3D 加速避免动画卡顿(尤其在移动端)。
  2. 复杂图形或大尺寸元素

    • 包含大量子元素的容器(如列表、卡片组)、大尺寸图片 / 视频,GPU 处理其渲染效率远高于 CPU。
  3. 需要避免重绘的元素

    • 元素频繁更新(如数字跳动、进度条),开启 3D 加速后,元素会被放入独立合成层,其变化不会触发父元素或其他元素的重排 / 重绘。

三、注意事项(避免滥用)

  1. 合成层过多的问题:每个开启 3D 加速的元素会创建独立的合成层,过多合成层会占用大量 GPU 内存(尤其移动端),可能导致页面卡顿、闪烁甚至崩溃。

  2. 避免无意义的 3D 变换

    • 不要对静态元素(无动画、无变换)使用 translateZ(0),纯属浪费资源。
    • 优先使用 will-change: transform 替代 translateZ(0),前者更符合规范且资源占用更合理。
  3. 可能引发的视觉问题

    • 3D 加速可能导致元素模糊(GPU 渲染精度问题),可配合 transform: translateZ(0) 同时设置 backface-visibility: hidden 缓解。
    • 部分浏览器对合成层元素的 z-index 处理特殊,可能导致层级错乱。

总结

可以开启 3D 加速的元素本质是设置了 3D 相关 transform 属性、will-change: transform 或父元素设置 perspective 的元素。核心目的是通过 GPU 提升动画和变换性能,但需按需使用,避免合成层过多导致的副作用。

4、如何做动画性能优化

动画性能优化的核心目标是减少浏览器渲染压力,避免卡顿(掉帧),确保动画流畅(理想状态下保持 60fps,即每帧渲染时间≤16.6ms)。以下是从渲染原理到具体实践的优化方案:

一、理解浏览器渲染流水线

浏览器渲染动画的三步骤(“渲染流水线”):

  1. 布局(Layout/Reflow) :计算元素的几何属性(位置、尺寸等),触发成本最高(会连锁影响其他元素)。
  2. 绘制(Paint/Repaint) :填充像素(如颜色、阴影),成本次之(大面积绘制耗时)。
  3. 合成(Composite) :将绘制好的图层合并为最终屏幕图像,成本最低。

优化原则:尽量避免触发布局和绘制,优先使用合成阶段的属性做动画。

二、具体优化策略

1. 避免频繁触发布局(Layout Thrashing)
  • 禁止读写布局属性交替进行:浏览器会缓存布局计算,若交替读取(如offsetWidth)和修改(如width)布局属性,会强制浏览器反复刷新布局,导致性能暴跌。✅ 优化:先批量读取,再批量修改。
  • 动画优先使用非布局属性:以下属性修改会触发布局,应避免用于动画:width/height/margin/padding/top/left/display/font-size等。✅ 替代方案:用transformopacity做动画(仅触发合成阶段,性能最优)。
2. 减少绘制区域和频率
  • 避免大面积绘制:动画元素尽量缩小范围,避免使用box-shadowgradient等绘制成本高的属性,减少z-index层级波动(可能导致图层重组)。
  • 使用will-changetransform: translateZ(0)触发硬件加速:告诉浏览器该元素即将动画,提前分配独立图层(GPU 加速绘制),但需避免滥用(过多图层会占用更多内存)。
  • 避免 “过度绘制” :检查工具:Chrome DevTools → More Tools → Layers,查看图层绘制次数(红色区域表示过度绘制),通过减少重叠元素、简化背景等优化。
3. 优化合成阶段
  • 控制图层数量:每个图层由 GPU 管理,过多图层(如数百个)会导致内存占用过高,甚至触发 GPU 内存限制(尤其移动设备),引发图层 “合并 / 拆分” 的额外开销。✅ 原则:仅为动画元素创建独立图层,非动画元素共享图层。
  • 避免图层闪烁或偏移:部分设备上,GPU 加速的元素可能因 “像素对齐” 问题出现模糊或偏移,可通过transform: translate3d(0,0,0)替代translate,或添加backface-visibility: hidden修复。
4. 其他关键技巧
  • 使用requestAnimationFrame代替setTimeout/setInterval
  • 简化动画逻辑:复杂计算(如物理引擎、大量元素动画)放在 Web Worker 中处理,避免阻塞主线程。
  • 避免强制同步布局:某些 API(如getBoundingClientRectgetComputedStyle)会强制浏览器立即计算布局,动画中应减少调用。
  • 使用 CSS 动画替代 JS 动画:CSS 动画由浏览器内部优化(如自动触发合成),性能优于 JS 动画(除非需要复杂交互逻辑)。✅ 推荐:@keyframes配合transform/opacity
5. 工具检测与调试
  • Chrome DevTools

    • Performance 面板:录制动画过程,查看帧率、布局 / 绘制耗时,定位性能瓶颈。
    • Layers 面板:分析图层数量、绘制区域、内存占用。
    • Rendering 面板:勾选 “Paint flashing”,可视化绘制区域;勾选 “FPS meter” 实时监控帧率。

总结

动画性能优化的优先级:

  1. 优先使用transformopacity做动画(仅触发合成)。
  2. 避免读写布局属性交替,减少布局次数。
  3. 合理使用硬件加速(控制图层数量)。
  4. 用工具检测并针对性优化(如减少过度绘制、简化逻辑)。

5、如何做内存泄露,页面卡顿问题的优化

一、内存泄露优化:先定位,再清除

内存泄露的本质是无用的内存无法被垃圾回收(GC)释放,需先定位泄露源,再针对性处理。

1. 定位内存泄露(工具先行)
  • 浏览器 DevTools(Memory 面板) :这是前端最常用的工具。通过 “堆快照(Heap snapshot)” 对比两次操作前后的内存差异,查看未被释放的对象(如 Detached DOM、未清除的定时器)。
  • Performance 面板:录制页面操作过程,查看内存曲线是否持续上升。若操作结束后内存未回落,大概率存在泄露。
  • 第三方工具:如Chrome Task Manager可实时查看页面内存占用,Lighthouse可批量检测潜在内存问题。
2. 常见泄露场景及解决方案

image.png

二、页面卡顿优化:减少主线程阻塞

页面卡顿的核心是主线程任务执行时间过长(如 JS 计算、DOM 操作),导致浏览器无法及时渲染(帧率低于 60fps)。

1. 定位卡顿瓶颈(聚焦主线程)
  • Performance 面板:录制页面操作,查看 “Main” 线程的任务耗时。红色长条表示耗时超过 50ms 的任务,需重点优化。
  • FPS 指标:通过DevTools > More tools > Rendering开启 “FPS meter”,实时查看帧率。帧率低于 50 时,用户会感知卡顿。
2. 核心优化方案
  1. 优化 JS 执行效率

    • 减少长任务:将超过 50ms 的同步任务拆分为多个小任务(用requestIdleCallbacksetTimeout),避免阻塞主线程。
    • 避免重复计算:用缓存(如MapWeakMap)存储重复计算的结果(如列表筛选、数据格式化)。
    • 优化循环:避免在循环内操作 DOM 或创建新对象,尽量将固定逻辑提到循环外。
  2. 减少 DOM 操作(重排 / 重绘)

    • 批量操作 DOM:用DocumentFragment批量插入元素,或先隐藏 DOM(display: none)再修改,最后显示。
    • 避免强制同步布局:不要在修改 DOM 后立即读取offsetWidthscrollTop等属性(会触发浏览器强制计算布局),尽量先读后改。
    • 使用 CSS 优化:用transformopacity实现动画(仅触发复合层,不触发重排重绘),避免用widthtop等属性。
  3. 资源加载与渲染优化

    • 懒加载:非首屏的图片、组件(如列表、弹窗)用IntersectionObserver实现懒加载,减少初始渲染压力。
    • 代码分割:用 Webpack、Vite 的代码分割功能,将非首屏代码(如路由组件)拆分为异步 chunk,避免首屏 JS 体积过大。
    • 优化 CSS:避免使用复杂选择器(如div:nth-child(2n)),减少 CSS 解析时间;用content-visibility: auto优化长列表渲染。

三、长效保障:监控与预防

优化后需建立监控机制,避免问题复发:

  • 实时监控:接入 Sentry、Fundebug 等工具,实时捕获内存泄露和卡顿异常,及时告警。
  • 性能预算:在项目中设置性能指标(如首屏加载时间<3s、内存占用<200MB),通过 CI/CD 流程拦截性能不达标代码。
  • 代码规范:制定开发规范,如 “组件卸载必须清除监听器”“避免全局变量”,从源头减少问题。

6、timeline和performance是如何分析定位性能问题

timeline(在Chrome DevTools中现在通常称为 Performance面板)和 performance(通常指 Performance API)是分析和定位性能问题的两个相辅相成的工具。

1. Performance面板(Timeline)- 宏观和可视化分析

Performance面板 就像一个高速摄像机,它记录下你的应用在特定时间段内发生的所有事情。它提供的是一个宏观的、可视化的、多维度关联的视图。

如何使用它定位问题:
  1. 录制:打开DevTools的Performance面板,点击“Record”按钮,执行你想要分析的用户操作(如页面加载、点击按钮、滚动等),然后停止录制。
  2. 分析录制的结果
    录制结果会生成一个包含多个轨道的报告,我们需要重点关注以下几个部分:

image.png

定位流程(Performance面板):
  1. 发现症状:录制后,先看FPS是否有红色块,CPU是否在某个时刻被长时间占满。
  2. 定位长任务:在Main Thread火焰图中找到那些很长的任务块(通常有红色角标)。
  3. 深入分析长任务:点击长任务,展开其调用栈。查看是哪些函数调用耗时最长。特别注意那些触发了Layout(布局)的函数调用。
  4. 关联分析:结合Network面板,看是否是某个资源加载完成后立即执行了复杂的脚本,导致了长任务。

2. Performance API - 精确和数据化测量

Performance API 是一组JavaScript接口,允许你在代码中精确地测量特定的性能指标和业务逻辑耗时。它提供的是微观的、精确到代码的、可编程的数据。

image.png

定位流程(Performance API):
  1. 定义关键用户路径:确定需要优化的业务流程,如“从点击搜索按钮到结果渲染完成”。

  2. 插入测量点:在代码的起始和结束位置使用 performance.mark() 或 performance.now()

  3. 收集数据:使用 PerformanceObserver 或 performance.getEntriesByType() 收集测量结果。

  4. 上报和分析:将收集到的性能数据上报到你的监控系统,进行聚合、分析和告警。你可以精确地知道是哪个函数、哪个接口、哪个组件渲染拖慢了整个流程。 image.png 协同工作流:

  5. 线上发现问题:通过 Performance API 上报的数据,你发现“商品加入购物车”这个操作在部分用户设备上平均耗时超过1秒,触发了告警。

  6. 本地复现和调查:在开发环境中,你打开 Performance面板,录制“点击加入购物车”这个操作。你发现在这个过程中有一个长达800ms的长任务,展开后发现是calculateRecommendations函数执行了复杂的计算,并且内部触发了多次强制同步布局

  7. 修复和验证:你优化了该函数的算法,并避免了强制同步布局。再次使用Performance面板录制,确认长任务已经消失,帧率恢复正常。

  8. 上线后监控:将修复后的代码部署到线上,继续通过 Performance API 监控“加入购物车”的耗时,确认指标已恢复正常。

简单来说:用Performance API在线上发现“什么慢了”,然后用Performance面板在本地深入分析“为什么慢”并找到解决方案。  两者结合,构成了从发现、定位到验证的完整性能优化闭环。

7、fp fcp如何计算

FP(First Paint,首次绘制)和 FCP(First Contentful Paint,首次内容绘制)的计算起点一致,但终点判定标准不同,最终通过浏览器 Performance API 输出具体时间。

二、如何获取计算结果

无需手动计算,可通过两种方式直接获取浏览器自动记录的 FP/FCP 时间。

1. 浏览器 DevTools 直接查看(用于调试)
  1. 打开 Chrome 浏览器,按F12打开 DevTools,切换到Performance面板。
  2. 勾选左上角 “Screenshots”(截图)选项,点击 “录制” 按钮(圆形图标)。
  3. 刷新页面,录制完成后,在 “Timings” 栏中可直接看到FPFCP的具体时间(单位:ms),同时截图会标注对应时刻的页面状态。
2. 通过 Performance API 代码获取(用于监控)

可在前端代码中嵌入逻辑,捕获 FP/FCP 时间并上报到监控平台(如 Sentry、阿里云监控),步骤如下:

image.png

三、关键影响因素与优化方向

FP 和 FCP 的时间直接受资源加载、代码执行效率影响,优化需聚焦 “减少首次绘制前的阻塞”。

  • 影响 FP 的核心因素:HTML 下载速度、<head>中阻塞渲染的资源(如同步 CSS)、浏览器初始化时间。
  • 影响 FCP 的核心因素:关键内容(如首屏文本、主图)的加载顺序、JS 执行对 DOM 解析的阻塞(如同步 JS)、服务器响应速度。

优化方向示例:

  1. 对 CSS 进行 “关键 CSS 内联”,非关键 CSS 异步加载(避免阻塞渲染)。
  2. 首屏图片使用loading="eager"并优化体积(如 WebP 格式、压缩),优先加载。
  3. 同步 JS 放在<body>底部,或用async/defer标记异步执行(避免阻塞 DOM 解析)。
  4. 启用服务器 Gzip/Brotli 压缩,减少 HTML/CSS/JS 的下载体积。

8、首屏加载如何优化

一、核心优化原则

  1. 更少: 减少关键资源的数量、大小和依赖链。
  2. 更快: 加快关键资源的获取速度。
  3. 更智能: 优先加载关键资源,非关键资源延迟加载。

二、具体优化手段

我们可以将优化过程分为几个阶段:

阶段一:网络与传输优化
  1. 启用 Gzip/Brotli 压缩

    • 做法: 在服务器(如 Nginx)上开启 Gzip 或更高效的 Brotli 压缩,对文本类文件(JS、CSS、HTML)进行压缩。
    • 效果: 通常能减少 60%-80% 的体积。
  2. 使用 CDN

    • 做法: 将静态资源(JS、CSS、图片、字体)部署到 CDN 上,利用其全球节点使用户从最近的服务器获取资源。
    • 效果: 显著降低网络延迟。
  3. 利用浏览器缓存

    • 做法: 通过设置 HTTP 缓存头(如 Cache-ControlETag),让浏览器缓存静态资源。对于长期不变的资源,可以使用强缓存(如 Cache-Control: max-age=31536000)。
    • 效果: 再次访问或页面跳转时,直接从本地磁盘读取,速度极快。
  4. 使用 HTTP/2 或 HTTP/3

    • 做法: 确保服务器支持并启用 HTTP/2 或 HTTP/3。
    • 效果: 多路复用、头部压缩等特性可以大幅提升资源加载效率,解决了 HTTP/1.1 的队头阻塞问题。
阶段二:资源优化
  1. 代码分割与懒加载

    • 做法:

      • 代码分割: 使用 Webpack、Vite 等构建工具的代码分割功能,将代码拆分成多个小块(chunk)。结合路由实现“按路由分割”,使每个页面只加载必要的代码。
      • 懒加载: 使用 import() 动态导入语法,对非首屏关键的组件、模块、图片进行懒加载,当它们需要显示时才去加载。
    • 效果: 显著减少首屏需要加载的 JS 包体积。

  2. Tree Shaking

    • 做法: 在 Webpack 等构建工具中,默认会启用 Tree Shaking。确保你的代码采用 ES6 模块化(import/export),这样构建工具可以安全地删除未被使用的代码(dead code)。
    • 效果: 减少最终打包文件的体积。
  3. 优化图片资源

    • 做法:

      • 选择合适的格式:WebP 格式通常比 PNG/JPEG 体积更小(需考虑浏览器兼容性)。
      • 使用图片压缩工具(如 Squoosh、TinyPNG)进行压缩。
      • 使用响应式图片(<picture> 和 srcset),为不同屏幕尺寸提供不同大小的图片。
      • 对首屏外图片使用懒加载(loading="lazy")。
    • 效果: 图片通常是体积最大的资源,优化效果立竿见影。

  4. 预加载关键资源

    • 做法: 使用 <link rel="preload"> 来告诉浏览器尽快加载关键资源,如关键 CSS、Web 字体、首屏图片。
    • 示例<link rel="preload" href="critical.css" as="style">
    • 效果: 提升关键资源的加载优先级,避免被其他资源阻塞。
  5. 预连接重要源

    • 做法: 使用 <link rel="preconnect"> 或 <link rel="dns-prefetch"> 与重要的第三方源(如 CDN、API 域名)提前建立连接。
    • 示例<link rel="preconnect" href="https://fonts.googleapis.com">
    • 效果: 减少建立 DNS 查询、TCP 连接和 TLS 握手的时间。
阶段三:渲染优化
  1. 优化关键渲染路径

    • 问题: CSS 和 JS 会阻塞渲染。

    • 做法:

      • CSS:

        • 将关键CSS(Above-the-Fold Content 所需样式)内联到 <head> 中,避免额外的网络请求。
        • 非关键CSS使用异步加载(如通过 <link rel="stylesheet" media="print" onload="this.media='all'" />)。
      • JS:

        • 将非关键的 JS 脚本标记为 async 或 defer,避免阻塞 HTML 解析。
        • defer:异步下载,在 DOM 解析完成后、DOMContentLoaded 事件前按顺序执行。
        • async:异步下载,下载完成后立即执行,不保证顺序。
  2. 避免客户端渲染带来的白屏问题

    • 问题: SPA 应用如果使用纯客户端渲染,在 JS 加载和执行完毕之前,页面是空的,导致白屏时间长。

    • 解决方案:

      • SSR: 服务端渲染。在服务器端生成完整的 HTML 返回给浏览器,用户能立即看到内容,然后再由客户端接管交互。Next.js, Nuxt.js 等框架内置支持。
      • SSG: 静态站点生成。适用于内容不常变化的页面,构建时直接生成 HTML,性能最好。
      • 骨架屏: 在数据加载期间,先展示一个与最终页面结构相似的灰色轮廓图,提升用户体验感知。
阶段四:构建与第三方优化
  1. 压缩和混淆 JavaScript/CSS

    • 做法: 使用 Terser 压缩 JS,使用 cssnano 压缩 CSS。这是构建工具的标配。
    • 效果: 移除不必要的字符、注释,缩短变量名,减小文件体积。
  2. 分析并优化第三方库

    • 做法:

      • 使用 Webpack-bundle-analyzer 分析打包产物,找出体积过大的第三方库。
      • 寻找更轻量级的替代方案(例如用 day.js 替代 moment.js)。
      • 只引入需要的功能(例如 import { debounce } from 'lodash-es' 而不是 import _ from 'lodash')。

三、工具与度量

优化不能靠猜,必须依赖数据。

  1. 性能度量工具

    • Lighthouse: 集成在 Chrome DevTools 中,提供全面的性能评分和优化建议。
    • WebPageTest: 提供更深入的性能分析,可以从全球不同地点进行测试。
    • Chrome DevTools Performance Panel: 用于分析运行时性能,查看帧率、网络请求、主线程活动等。
  2. 核心 Web 指标
    关注 Google 提出的 Core Web Vitals,它们直接影响用户体验和 SEO:

    • LCP: 最大内容绘制。衡量加载性能。目标:2.5 秒内。优化手段:优化服务器响应、缓存、CDN、压缩图片、预加载关键资源、移除渲染阻塞资源。
    • FID: 首次输入延迟。衡量交互性。目标:100 毫秒内。优化手段:分解长任务、优化 JavaScript(代码分割、Tree Shaking)、使用 Web Worker。
    • CLS: 累积布局偏移。衡量视觉稳定性。目标:小于 0.1。优化手段:为图片和视频设置明确的 width 和 height 属性,避免在现有内容上方动态插入内容,使用 transform 动画替代影响布局的属性动画。

总结与工作流

一个推荐的优化工作流是:

  1. 测量: 使用 Lighthouse 或 WebPageTest 对线上或本地环境进行测试,记录核心指标。
  2. 分析: 查看报告,识别性能瓶颈(是资源太大?还是渲染被阻塞?)。
  3. 实施: 根据瓶颈选择上述对应的优化手段进行实施。
  4. 验证: 再次测量,确认优化是否生效。
  5. 监控: 对线上环境的 Core Web Vitals 进行持续监控(例如使用 Google Search Console)。

首屏优化是一个持续的过程,需要根据项目特点和用户数据不断调整策略。从最容易实现的“低垂的果实”(如压缩、缓存、图片优化)开始,往往能获得最大的初始收益。

9、首屏js如何单独抽离出来?

它的核心思想是将首次渲染所必需的代码(首屏JS)与非必需的代码(非首屏JS、第三方库、异步组件代码等)分离开,从而最小化关键资源体积。

下面我以最常用的构建工具 Webpack 和 Vite 为例,详细说明如何实现。

核心概念:代码分割

无论使用什么工具,实现抽离的基础都是代码分割。主要有两种方式:

  1. 多入口点:手动指定多个入口文件。
  2. 动态导入:在代码中使用 import() 语法,构建工具会自动进行分割。这是更主流和灵活的方式。

方法一:使用 Webpack 进行抽离

Webpack 提供了多种配置方式来实现代码分割。

1. 配置多入口(基础分离)

适用于简单项目,将首屏代码和非首屏代码手动写在不同的文件里。

// webpack.config.js
module.exports = {
  // 配置两个入口
  entry: {
    main: './src/main.js',       // 非首屏、公共逻辑
    home: './src/home-index.js', // 首屏专属入口
  },
  output: {
    filename: '[name].bundle.js', // 输出 main.bundle.js 和 home.bundle.js
    path: __dirname + '/dist',
  },
  // ... 其他配置
};

缺点:不够智能,需要手动维护入口文件,公共依赖容易重复打包。

2. 使用 splitChunks 优化分包(推荐)

这是最常用、最强大的方法。它允许你根据策略(如 node_modules)自动拆分公共依赖。

 optimization: {
    splitChunks: {
      chunks: 'all', // 对所有类型的 chunk 进行拆分(async, initial, all)
      cacheGroups: {
        // 抽离第三方库
        vendor: {
          test: /[\/]node_modules[\/]/, // 匹配 node_modules 下的模块
          name: 'vendors',
          chunks: 'all',
          priority: 20, // 优先级
        },
        // 抽离公共工具函数、组件等
        common: {
          name: 'common',
          minChunks: 2, // 被至少2个入口 chunk 引用才拆分
          chunks: 'all',
          priority: 10,
          reuseExistingChunk: true,
        },
      },
    },
    //  webpack 运行时代码抽离为单个文件
    runtimeChunk: {
      name: 'runtime',
    },
  },
3. 使用动态导入实现按路由/组件分割(最智能)

在你的应用代码中,使用 import() 语法来标记分割点。结合 React/Vue 等框架的路由懒加载,这是最佳实践。

React:
// 1. 非首屏的路由组件使用 React.lazy 和 import()
import { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

// 首屏组件直接导入(会打包进主包)
import Home from './pages/Home';
// 非首屏组件懒加载(会被单独打包成一个 chunk)
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
Vue:
import Header from './Header.vue'; // 首屏 JS 同步导入
// 非首屏组件异步导入(Vite 自动打包为单独文件)
const LazyComponent = defineAsyncComponent(() => import('./LazyComponent.vue'));

方法二:使用 Vite 进行抽离

Vite 基于 ES Modules,开箱即提供了优秀的代码分割能力。它的配置更简单直观。

1. 手动配置 rollupOptions(与 Webpack 的 splitChunks 类似)
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        // 手动指定 chunk 分割策略
        manualChunks: {
          // 将 React 相关库拆分为一个 chunk
          'react-vendor': ['react', 'react-dom'],
          // 将工具库拆分为一个 chunk
          'utility-vendor': ['lodash', 'axios'],
          // 注意:Vite 4+ 默认策略已很好,手动配置可用于更细粒度控制
        },
      },
    },
  },
});
2. 使用动态导入(与 Webpack 完全相同)

Vite 完全支持 import() 语法,用法和上面的 React/Vue 示例一模一样。你不需要额外配置,Vite 会自动处理代码分割。

// 在 Vite 项目中,这行代码会自动创建一个单独的 chunk
const About = lazy(() => import('./pages/About'));

总结:最佳实践路径

  1. 首要步骤:使用 动态导入 import()  对你的路由和大型非首屏组件进行懒加载。这是性价比最高的方法。
  2. 进阶优化:配置构建工具的 splitChunks / manualChunks,将庞大的、不常变的第三方库(如 React, Vue, Lodash)拆分成独立的、可长期缓存的 vendor 包。
  3. 持续监控:使用 打包分析工具 定期检查打包结果,防止某些依赖意外地把所有代码打包到一起,确保分割策略有效。

10、react hooks为啥不能在块里面用?

React Hooks 不能在条件语句、循环、嵌套函数等代码块中使用,核心原因是 React 依赖 Hooks 的调用顺序来维护其内部状态和副作用的关联关系。具体可以从以下几个角度理解:

1. React Hooks 的工作原理:依赖「调用顺序」

React 内部通过一个 单向链表 来管理组件中的 Hooks(如 useStateuseEffect 等)。每次组件渲染时,React 会按顺序遍历这个链表,将 Hook 的状态、副作用等信息与当前渲染关联起来。

2. 代码块中使用 Hooks 会破坏「调用顺序的一致性」

如果在条件语句、循环等代码块中使用 Hooks,会导致 不同渲染周期中 Hooks 的调用顺序不一致,React 无法正确关联状态和 Hook,最终引发状态错乱或报错。

3. React 的强制校验:确保顺序一致性

为了避免上述问题,React 内部有严格的校验机制:每次渲染时,Hooks 必须在组件顶层以相同的顺序被调用。如果检测到 Hooks 在代码块中使用(可能导致顺序不一致),React 会在开发环境抛出错误:

4. 如何「绕开」限制?本质是「拆分逻辑」

如果需要根据条件执行类似 Hook 的逻辑,正确的做法是 将条件判断移到 Hook 内部,而非外部包裹 Hook

image.png

image.png

总结

React Hooks 不能在代码块中使用,根本原因是 Hooks 依赖「每次渲染时的调用顺序一致」来维护状态关联。代码块会破坏这种顺序一致性,导致状态错乱。遵循「只在组件顶层调用 Hooks」的规则,本质是让 React 能可靠地管理 Hooks 的生命周期和状态。

11、node的settimeout和nexttick有什么区别

在 Node.js 中,setTimeout 和 process.nextTick 都用于延迟代码执行,但它们属于不同的任务队列,执行时机、优先级和用途有显著区别。核心差异源于 Node.js 的 事件循环(Event Loop)机制

1. 所属的任务队列不同

Node.js 的事件循环分为多个阶段(如 timers、poll、check 等),每个阶段处理特定类型的任务,而 setTimeout 和 process.nextTick 分属不同的队列:

  • setTimeout:属于 timers 阶段队列用于执行延迟指定时间(毫秒)后的回调函数,回调会被放入 timers 阶段的任务队列,等待事件循环进入该阶段时执行。
  • process.nextTick:属于 nextTick 队列这是一个独立于事件循环各阶段的「微任务队列」,优先级最高,会在 当前操作完成后、事件循环进入下一个阶段前 立即执行。

2. 执行时机与优先级

事件循环的简化流程是:timers 阶段 → I/O 回调阶段 → idle/prepare → poll 阶段 → check 阶段 → close 回调阶段每个阶段执行完后,会先清空 nextTick 队列,再进入下一个阶段。

  • process.nextTick 执行更早:无论当前处于事件循环的哪个阶段,只要执行完该阶段的任务,就会立即处理 nextTick 队列中的所有回调(直到清空),再进入下一个阶段。例如:在 poll 阶段执行完后,会先跑所有 nextTick 回调,再进入 check 阶段。
  • setTimeout 执行较晚:回调被放入 timers 阶段队列,必须等待事件循环循环到 timers 阶段时才会执行(即使延迟时间设为 0,也需等待当前阶段结束后,进入 timers 阶段才会执行)。

副作用与使用场景

  • process.nextTick

    • 优点:优先级极高,可在当前操作完成后立即执行(如修正对象状态、避免异步操作导致的不一致)。
    • 风险:如果嵌套调用 nextTick,会阻塞事件循环(因为会一直清空 nextTick 队列,无法进入下一个阶段),导致 I/O 回调、定时器等任务延迟执行。
    • 场景:同步操作后的收尾工作(如初始化对象后立即触发回调)、修复异步逻辑中的时序问题。
  • setTimeout(fn, 0)

    • 优点:不会阻塞事件循环,回调会在事件循环的 timers 阶段执行,给其他任务(如 I/O)留出执行机会。
    • 缺点:延迟时间不精确(即使设为 0,实际延迟至少为 1ms,且受事件循环繁忙程度影响),优先级低于 nextTick
    • 场景:需要将耗时操作放到「下一轮事件循环」执行,避免阻塞当前同步流程(如批量处理数据时拆分任务)。

image.png

12、node如何区分是文件还是文件夹

主要方法总结

  • fs.stat() / fs.statSync() : 获取文件/文件夹的状态信息
  • fs.promises.stat() : Promise 版本的 stat
  • stats.isFile() : 判断是否为文件
  • stats.isDirectory() : 判断是否为文件夹
  • stats.isSymbolicLink() : 判断是否为符号链接

推荐使用 fs.promises API,因为它更现代且支持 async/await 语法,代码更简洁易读。

13、node使用过哪些功能,api

Node.js 作为一个基于 Chrome V8 引擎的 JavaScript 运行时,提供了丰富的内置模块和 API,涵盖文件操作、网络通信、进程管理、流处理等多个领域。以下是其核心功能及常用 API 的分类整理:

一、文件系统操作(fs 模块)

用于读写文件、操作目录、获取文件信息等,支持同步、异步(回调)和 Promise 三种方式。核心 API

  • fs.readFile(path, callback) / fs.promises.readFile():异步读取文件内容
  • fs.writeFile(path, data, callback):异步写入文件(覆盖原内容)
  • fs.appendFile(path, data, callback):异步追加内容到文件
  • fs.stat(path, callback) / fs.statSync():获取文件 / 目录状态(区分文件 / 文件夹的核心方法,如 stats.isFile()stats.isDirectory()
  • fs.mkdir(path, { recursive: true }, callback):创建目录(recursive: true 支持创建多级目录)
  • fs.rm(path, { recursive: true, force: true }, callback):删除文件或目录(recursive: true 可删除非空目录)
  • fs.readdir(path, { withFileTypes: true }, callback):读取目录内容(withFileTypes 可直接获取子项类型,无需二次调用 stat

二、路径处理(path 模块)

用于处理文件路径(跨平台兼容,自动处理 / 和 `` 差异)。核心 API

  • path.join(...paths):拼接路径片段(自动处理多余的分隔符)
  • path.resolve(...paths):解析为绝对路径(以当前工作目录为基准)
  • path.basename(path):获取路径中的文件名(含扩展名)
  • path.dirname(path):获取路径中的目录名
  • path.extname(path):获取文件扩展名(如 .txt
  • path.isAbsolute(path):判断是否为绝对路径

三、网络通信(http/https 模块)

用于创建 HTTP/HTTPS 服务器或客户端,处理网络请求。核心 API

  • http.createServer((req, res) => { ... }):创建 HTTP 服务器,req 为请求对象(含 urlmethod 等),res 为响应对象(res.write()res.end() 发送响应)
  • server.listen(port, host, callback):启动服务器监听指定端口
  • http.request(options, callback):发送 HTTP 请求(客户端)
  • res.writeHead(statusCode, headers):设置响应状态码和头信息

四、流(stream 模块)

用于处理大文件或持续的数据(如视频、日志),通过分块传输减少内存占用。核心类型

  • 可读流(Readable):如 fs.createReadStream()
  • 可写流(Writable):如 fs.createWriteStream()
  • 双工流(Duplex):既可读又可写(如网络套接字)
  • 转换流(Transform):处理数据转换(如 zlib 压缩、crypto 加密)核心 API
  • stream.pipe(destination):管道操作(将可读流数据自动传入可写流,自动处理背压)
  • on('data', callback):监听数据块传输
  • on('end', callback):监听流结束

五、进程管理(process 模块)

用于访问当前 Node.js 进程信息,控制进程行为(全局对象,无需 require)。核心 API

  • process.argv:获取命令行参数(数组,第一个为 Node 路径,第二个为脚本路径,后续为自定义参数)
  • process.env:获取环境变量(如 process.env.NODE_ENV
  • process.cwd():获取当前工作目录
  • process.exit(code):终止当前进程(code=0 表示正常退出)
  • process.on('exit', callback):监听进程退出事件

六、子进程(child_process 模块)

用于创建子进程,执行系统命令或其他脚本(避免阻塞主进程)。核心 API

  • child_process.exec(command, callback):执行命令并缓存输出(适合短命令)
  • child_process.spawn(command, args):启动子进程并实时获取输出(适合长命令,如 npm install
  • child_process.fork(modulePath):创建 Node.js 子进程(用于并行计算,支持父子进程通信)

七、模块系统(module/require

Node.js 的模块化机制,通过 require 加载模块,module.exports 导出模块。核心 API

  • require(modulePath):加载内置模块、第三方模块或自定义模块
  • module.exports / exports:导出模块内容(exports 是 module.exports 的引用)
  • __dirname:当前模块所在目录的绝对路径(仅在 CommonJS 模块中有效)
  • __filename:当前模块文件的绝对路径

八、异步编程(util/Promise

Node.js 原生支持异步操作,提供工具简化异步代码。核心 API

  • util.promisify(fn):将回调风格的函数转换为 Promise 风格(如 const readFile = util.promisify(fs.readFile)
  • async/await:语法糖,简化 Promise 链式调用(需配合 async 函数使用)
  • setTimeout / setInterval:定时器(与浏览器 API 类似,但基于 Node.js 事件循环)

九、加密与安全(crypto 模块)

用于数据加密、解密、哈希计算等安全操作。核心 API

  • crypto.createHash(algorithm):创建哈希对象(如 'sha256' 算法),用于生成数据指纹
  • crypto.createCipheriv(algorithm, key, iv) / crypto.createDecipheriv():对称加密 / 解密(如 AES 算法)
  • crypto.randomBytes(size, callback):生成随机字节(用于生成密钥、盐等)

十、其他常用模块

  • os:获取操作系统信息(如 os.totalmem() 总内存、os.cpus() CPU 信息)
  • url:解析 URL 字符串(new URL(urlString) 生成 URL 对象,方便获取 hrefpathname 等)
  • querystring:解析 URL 查询参数(如 querystring.parse('name=foo&age=18')
  • zlib:数据压缩 / 解压(如 zlib.gzip() 压缩、zlib.unzip() 解压)

以上是 Node.js 最核心的功能和 API,覆盖了后端开发的大部分场景(如服务器开发、命令行工具、文件处理等)。实际开发中,还会结合第三方库(如 expresskoa 用于 Web 框架,mongoose 用于 MongoDB 操作)扩展功能。需要某个模块的具体用法可以进一步说明!

14、node中间件

Node.js 中的中间件(Middleware)是一种核心机制,尤其在 Web 开发中(如 Express、Koa 等框架)扮演关键角色。它本质是处理请求 / 响应的函数,用于在请求到达最终处理逻辑前、或响应返回客户端前,执行一系列通用或特定的操作,形成 “流水线” 式的处理流程。

核心概念

  • 本质:一个函数,接收请求对象(req)、响应对象(res)和一个用于触发下一个中间件的函数(next,Koa 中为 next() 异步函数)。
  • 作用:预处理请求(如解析参数、验证权限)、记录日志、处理错误、扩展功能等。
  • 流程:按注册顺序链式执行,通过 next() 传递控制权,直到完成响应或出错。

Express 中间件(经典示例)

Express 的中间件机制简单直观,是理解中间件的最佳入门。

const express = require('express');
const app = express();

// 中间件函数:(req, res, next) => {}
app.use((req, res, next) => {
  console.log('请求到达中间件');
  // 必须调用 next(),否则请求会被挂起
  next(); // 传递给下一个中间件/路由
});

// 路由处理(可视为特殊中间件)
app.get('/', (req, res) => {
  res.send('Hello World');
});

app.listen(3000);
2. 分类与场景

image.png

image.png

image.png

关键原则

  1. 顺序敏感:先注册的中间件先执行(如解析请求体的中间件必须在路由前)。
  2. 必须调用 next () :除非要提前返回响应(如权限校验失败直接返回 403),否则需调用 next() 传递请求。
  3. 错误传递:通过 next(err) 将错误传给错误处理中间件(Express),或 try/catch 捕获(Koa)。
  4. 单一职责:一个中间件只做一件事(如日志、权限、压缩应分离)。

常用第三方中间件

  • 功能扩展cors(跨域)、helmet(安全头)、compression(响应压缩)。
  • 数据处理multer(文件上传)、express-validator(参数校验)。
  • 身份验证passport(认证策略)、jsonwebtoken(JWT 验证)。
  • 日志监控morgan(请求日志)、winston(日志管理)。

15、如何定位解决线上的问题

定位和解决线上问题是后端开发中至关重要的能力,需要结合监控、日志、调试工具和系统设计经验,按步骤逐步排查。以下是一套系统化的流程和方法:

一、线上问题的常见类型

先明确问题范畴,便于针对性排查:

  • 功能异常:接口报错、业务逻辑错误(如订单状态异常)
  • 性能问题:接口响应慢、CPU / 内存飙升、数据库卡顿
  • 可用性问题:服务崩溃、超时、5xx 错误、集群节点异常
  • 数据问题:数据丢失、不一致、脏数据

二、排查前的准备:快速止损

线上问题往往影响用户,先止损再根因分析

  1. 确认影响范围

    • 是单个用户 / 地区,还是全量用户?
    • 是特定接口 / 功能,还是整个服务?
    • 查看监控面板(如 Grafana、Prometheus)的错误率、响应时间、机器负载等指标。
  2. 临时解决方案

    • 若刚发布新版本,立即回滚到上一个稳定版本(通过 CI/CD 工具如 Jenkins、GitLab CI)。
    • 若某台机器异常,通过负载均衡(如 Nginx、SLB)下线故障节点。
    • 若数据库慢查询,临时 kill 阻塞进程,或添加索引缓解。
    • 若流量突增,开启限流、熔断(如使用 Sentinel、Hystrix)。

三、核心排查步骤:定位根因

1. 日志分析:从日志中找线索

日志是排查问题的核心依据,需确保关键流程有日志输出(如请求参数、响应结果、异常堆栈)。

  • 关键日志类型

    • 应用日志:接口输入输出、业务逻辑分支、异常堆栈(用 winstonpino 等工具记录,区分 info/warn/error 级别)。
    • 访问日志:HTTP 请求的 method、url、status、耗时、IP(如 Nginx 访问日志、Express 的 morgan 日志)。
    • 系统日志:服务器 CPU、内存、磁盘 IO(如 dmesg/var/log/messages)。
    • 数据库日志:慢查询日志、死锁日志(如 MySQL 的 slow_query_log)。
  • 日志查询技巧

    • 按时间范围过滤:定位问题发生的精确时间段(如 grep "2025-11-03 14:30:" app.log)。
    • 按关键字搜索:错误信息(如 Error500)、用户 ID、订单号等唯一标识(如 grep "orderId=12345" app.log)。
    • 结合堆栈信息:异常日志的堆栈跟踪(stack trace)能直接定位代码行(如 TypeError: Cannot read property 'x' of undefined 提示变量未定义)。
    • 分布式追踪:若用微服务,通过链路追踪工具(如 Jaeger、Zipkin)串联跨服务日志,定位哪个服务出问题。
2. 监控指标:定位性能瓶颈

通过监控工具观察系统指标,判断是哪一层(应用、数据库、网络、服务器)出问题:

  • 应用层指标

    • 错误率(5xx/4xx 占比)、接口响应时间(P95/P99 分位值)。
    • Node.js 进程指标:event loop 延迟(eventLoopLag)、堆内存(heapUsed)、活跃句柄数(handles/requests)—— 可用 clinic.jsnode-statsd 监控。
  • 数据库指标

    • 连接数(是否超过最大连接池)、慢查询次数、锁等待时间。
    • 读写吞吐量(QPS)、索引命中率(避免全表扫描)。
  • 服务器 / 容器指标

    • CPU 使用率(是否持续 >80%)、内存使用率(是否内存泄漏)、磁盘空间(是否满了)、网络 IO(是否带宽瓶颈)。
  • 工具推荐

    • 指标收集:Prometheus + Node Exporter(服务器)、Mongodb Exporter(数据库)。
    • 可视化:Grafana(绘制仪表盘,设置告警)。
    • 告警:通过邮件、钉钉、Slack 实时推送异常指标(如 CPU 过高、错误率突增)。
3. 复现问题:本地 / 测试环境验证

若线上日志和监控不足以定位问题,需尝试复现:

  • 复现步骤

    • 用线上相同的输入参数(如用户 ID、请求体)在测试环境调用接口。
    • 检查是否有环境差异:配置(如数据库地址、API 密钥)、依赖版本(package.json 锁定版本)、服务器配置(内存、CPU 核数)。
    • 若涉及分布式系统,需模拟网络延迟、服务降级等场景(用 tc 命令模拟网络延迟,chaosblade 注入故障)。
  • 调试工具

    • Node.js 本地调试:用 --inspect 启动进程,通过 Chrome DevTools 断点调试(适合本地复现的问题)。
    • 线上临时调试:用 clinic.js flame 生成火焰图分析 CPU 瓶颈,clinic.js heap-profiler 分析内存泄漏(注意:线上调试可能影响性能,建议在非峰值或备用节点操作)。
4. 代码与配置检查:排除人为疏漏
  • 代码层面

    • 近期发布的代码变更(用 git diff 对比上一版本),重点看逻辑分支、异常处理(如是否漏了 try/catch)、异步操作(如未正确处理 Promise 拒绝)。
    • 边界条件:空值处理(null/undefined)、大数溢出、时间格式转换(如跨时区问题)。
  • 配置层面

    • 环境变量是否正确(如 NODE_ENV 是 production 还是 development,数据库连接字符串是否正确)。
    • 资源限制:Node.js 内存上限(--max-old-space-size)、数据库连接池大小、Nginx 最大并发数。
5. 分布式 / 集群问题:排查节点差异

若服务部署在多节点集群:

  • 检查是否只有特定节点异常(如某台机器磁盘损坏、网络不通)。
  • 确认负载均衡是否均衡(是否某节点流量过高)。
  • 分布式锁、缓存一致性问题:如 Redis 主从同步延迟导致的数据不一致,需检查缓存更新逻辑。

四、解决问题与预防措施

  1. 修复问题

    • 针对根因修改代码(如修复逻辑漏洞、优化慢查询、增加异常处理)。
    • 发布前在测试环境充分验证(单元测试、集成测试、压力测试)。
    • 灰度发布:先发布到少量节点,观察指标无异常后全量发布。
  2. 预防措施

    • 完善监控与告警:为关键指标(错误率、响应时间、CPU 等)设置阈值告警,避免问题扩大。
    • 日志规范化:确保关键流程日志完整(如用户操作、系统异常),使用结构化日志(JSON 格式)便于检索。
    • 自动化测试:覆盖核心业务逻辑,避免重复引入同类问题。
    • 灾备演练:定期进行故障注入(如断网、杀进程),验证系统容错能力。
    • 文档沉淀:记录线上问题的根因、解决方案和预防措施,形成知识库。

总结

线上问题排查的核心逻辑是: “先止损,再定位,后解决,最后预防” 。关键在于利用好日志和监控工具,从现象倒推根因,同时通过流程和工具减少问题发生的概率。遇到复杂问题时,可结合团队协作(如与运维、DBA 配合),快速定位跨系统瓶颈。

16、如何做docker部署

使用 Docker 部署 Node.js 应用可以实现环境一致性、快速扩缩容和简化部署流程。以下是完整的 Docker 部署步骤,从环境准备到容器运行、进阶优化:

一、准备工作

  1. 安装 Docker确保服务器 / 本地环境已安装 Docker,参考 官方文档。验证安装:docker --version(输出版本信息即成功)。

  2. Node.js 应用结构假设你的应用目录结构如下(核心是 package.json 和入口文件,如 app.js):

    plaintext

    my-node-app/
    ├── app.js          # 应用入口
    ├── package.json    # 依赖配置
    ├── package-lock.json
    └── Dockerfile      # 稍后创建的构建文件
    

二、编写 Dockerfile

Dockerfile 是构建 Docker 镜像的 “食谱”,定义了镜像的构建步骤。以下是针对 Node.js 应用的优化版 Dockerfile

dockerfile

# 1. 基础镜像(选择 Node.js 官方镜像,推荐 alpine 轻量版本)
FROM node:18-alpine

# 2. 设置工作目录(容器内的目录,后续命令在此执行)
WORKDIR /app

# 3. 复制 package.json 和 package-lock.json(优先复制依赖文件,利用 Docker 缓存)
COPY package*.json ./

# 4. 安装依赖(生产环境移除 devDependencies)
RUN npm ci --only=production

# 5. 复制应用代码(仅复制必要文件,配合 .dockerignore 排除无关文件)
COPY . .

# 6. 暴露端口(Node.js 应用监听的端口,如 3000)
EXPOSE 3000

# 7. 启动命令(使用 exec 格式,确保信号能正确传递给 Node 进程)
CMD ["node", "app.js"]

三、创建 .dockerignore 文件

类似 .gitignore,用于排除不需要打包到镜像的文件,减小镜像体积并避免敏感信息泄露:

plaintext

# 排除依赖目录(镜像中会重新安装)
node_modules
npm-debug.log

# 排除版本控制文件
.git
.gitignore

# 排除本地环境配置
.env
*.md

# 排除构建产物(如适用)
dist/*.map

四、构建 Docker 镜像

在应用根目录执行以下命令,构建镜像(-t 指定镜像名称和标签,如 my-node-app:v1):

bash

docker build -t my-node-app:v1 .
  • 构建成功后,通过 docker images 可查看镜像列表。

五、运行 Docker 容器

使用构建好的镜像启动容器,映射端口并配置运行参数:

1. 基础运行(前台启动,适合测试)

bash

docker run -p 3000:3000 my-node-app:v1
  • -p 3000:3000:将容器内的 3000 端口映射到宿主机的 3000 端口(外部可通过 http://宿主机IP:3000 访问)。
2. 后台运行(生产环境推荐)

bash

docker run -d -p 3000:3000 --name my-node-container my-node-app:v1
  • -d:后台运行容器。
  • --name:指定容器名称,方便后续操作(如停止、重启)。
3. 挂载目录(开发时实时同步代码)

开发阶段可挂载本地目录到容器,避免每次改代码都重新构建镜像:

bash

docker run -d -p 3000:3000 -v $(pwd):/app my-node-app:v1
  • -v $(pwd):/app:将当前目录(宿主机)挂载到容器内的 /app 目录。
4. 配置环境变量(传递参数)

通过 -e 传递环境变量(如数据库地址、密钥):

bash

docker run -d -p 3000:3000 \
  -e NODE_ENV=production \
  -e DB_HOST=mysql-host \
  --name my-node-container my-node-app:v1

七、进阶:使用 Docker Compose 管理多容器

如果应用依赖其他服务(如 MySQL、Redis),可通过 docker-compose.yml 定义多容器服务,一键启动所有依赖。

  1. 创建 docker-compose.yml

  2. 启动所有服务:

    bash

    docker-compose up -d  # 后台启动
    
  3. 其他常用命令:

    • docker-compose logs -f:查看所有服务日志
    • docker-compose down:停止并删除所有容器(数据卷保留)
    • docker-compose restart app:仅重启 app 服务

八、生产环境优化建议

  1. 镜像优化

    • 使用 alpine 基础镜像(体积小,如 node:18-alpine 约 100MB,而 node:18 约 900MB)。
    • 多阶段构建:分离构建和运行环境(如前端项目先在 Node 镜像打包,再拷贝到 Nginx 镜像)。
  2. 安全加固

    • 避免在容器内使用 root 用户,在 Dockerfile 中创建普通用户:

      dockerfile

      RUN addgroup -S appgroup && adduser -S appuser -G appgroup
      USER appuser  # 切换到普通用户运行
      
    • 不把敏感信息(如密码)硬编码到镜像,通过环境变量或 secrets 管理。

  3. 资源限制

    • 启动容器时限制 CPU 和内存,避免单个容器耗尽资源:

      bash

      docker run -d -p 3000:3000 --cpus 0.5 --memory 512m my-node-app:v1
      
  4. 日志与监控

    • 容器日志通过 docker logs 或集中化工具(如 ELK、Promtail + Loki)收集。
    • 用 docker stats 监控容器资源使用,或集成 Prometheus + Grafana。
  5. 持续部署

    • 结合 CI/CD 工具(如 GitHub Actions、GitLab CI),实现代码提交后自动构建镜像、部署容器。

通过以上步骤,即可完成 Node.js 应用的 Docker 化部署,实现 “一次构建,到处运行” 的目标。

17、h5首屏如何加载

H5 首屏加载速度直接影响用户体验和留存率,优化核心是减少首屏资源体积、缩短请求时间、优先加载关键内容。以下是从资源优化、加载策略到工程化方案的完整优化路径:

一、核心指标:首屏加载的关键衡量标准

优化前需明确目标指标,避免盲目优化:

  • FP(First Paint) :首次绘制(页面出现第一个像素的时间)。
  • FCP(First Contentful Paint) :首次内容绘制(文本、图片等有意义内容出现的时间)。
  • LCP(Largest Contentful Paint) :最大内容绘制(首屏中最大元素加载完成的时间,核心指标,目标 < 2.5s)。
  • TTI(Time to Interactive) :可交互时间(页面完全就绪,用户操作无延迟的时间)。

二、资源优化:减少首屏需要加载的内容

首屏加载慢的根本原因往往是资源体积过大或请求过多,需从 “减体积” 和 “少请求” 入手。

1. HTML 优化:精简首屏 HTML
  • 移除冗余代码:删除首屏不需要的注释、空白、隐藏元素(display: none)。

  • 内联关键 CSS:将首屏必需的 CSS 直接写在 <style> 标签内(避免额外请求 CSS 文件的阻塞),非关键 CSS 异步加载。

    html

    预览

    <head>
      <!-- 内联首屏关键 CSS -->
      <style>
        .header { height: 60px; }
        .banner { background: #f5f5f5; }
        /* 仅包含首屏可见元素的样式 */
      </style>
      <!-- 非关键 CSS 异步加载 -->
      <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>
    
  • 避免首屏加载阻塞脚本<script> 标签默认阻塞 HTML 解析,非首屏必需的脚本用 async 或 defer 异步加载。

    html

    预览

    <!-- 异步加载,不阻塞 HTML 解析和渲染 -->
    <script src="analytics.js" async></script>
    <!-- 延迟到 HTML 解析完成后执行 -->
    <script src="utils.js" defer></script>
    
2. CSS/JS 优化:压缩与拆分
  • 压缩代码

    • CSS:用 cssopostcss 压缩,移除无用样式(如 purgecss 配合 Tailwind 清理未使用的类)。
    • JS:用 terser 压缩(Webpack、Vite 默认集成),开启 tree-shaking 移除死代码。
  • 代码拆分(Code Splitting)

    • 按路由拆分:首屏只加载当前路由的 JS/CSS,其他路由按需加载(Webpack 的 splitChunks、Vite 的 import() 动态导入)。

      javascript

      运行

      // 路由懒加载示例(Vue/React 通用)
      const Home = () => import(/* webpackChunkName: "home" */ './pages/Home');
      
    • 提取公共库:将 vuereact 等第三方库拆分为单独的 vendor.js,利用浏览器缓存。

3. 图片优化:首屏图片是重灾区
  • 选择合适格式

    • 静态图:优先用 WebP 或 AVIF(比 JPEG/PNG 小 30%-50%),降级兼容(用 <picture> 标签)。

      html

      预览

      <picture>
        <source srcset="banner.avif" type="image/avif">
        <source srcset="banner.webp" type="image/webp">
        <img src="banner.jpg" alt="首屏 banner">
      </picture>
      
    • 简单图形:用 SVG 替代 PNG(矢量图,体积小且缩放清晰)。

  • 压缩与裁剪

    • 压缩工具:用 squoosh.app 手动压缩,或构建时集成 image-webpack-loader 自动压缩。

    • 按需裁剪:根据设备尺寸加载不同分辨率图片(srcset + sizes)。

      html

      预览

      <img 
        src="banner-small.jpg" 
        srcset="banner-small.jpg 600w, banner-large.jpg 1200w"
        sizes="(max-width: 768px) 600px, 1200px"
        alt="响应式 banner"
      >
      
  • 懒加载非首屏图片:首屏外的图片用 loading="lazy" 延迟加载(浏览器原生支持)。

    html

    预览

    <!-- 首屏图片不懒加载,非首屏图片懒加载 -->
    <img src="hero.jpg" alt="首屏主图">
    <img src="footer.jpg" alt="底部图片" loading="lazy">
    
4. 字体优化:避免 FOIT(字体加载闪烁)
  • 只加载必要字重和字符

    • 中文字体体积大(如思源黑体完整包 > 10MB),用 font-spider 提取首屏用到的字符(字蛛)。
    • 优先用系统预装字体(如 PingFang SCMicrosoft YaHei)作为 fallback。
  • 异步加载字体:用 font-display: swap 避免字体加载时文本不可见。

    css

    @font-face {
      font-family: 'MyFont';
      src: url('myfont.woff2') format('woff2');
      font-display: swap; /* 字体加载中显示 fallback 字体 */
    }
    

三、加载策略:优先关键资源,延迟非关键资源

通过合理的加载顺序和预加载策略,让首屏关键内容更快呈现。

1. 关键资源优先加载
  • 首屏内容最小化:只保留首屏可见的元素(如导航、Banner、主要按钮),其他内容(如下拉菜单、页脚)通过滚动加载或延迟渲染。

  • 预加载关键资源:用 <link rel="preload"> 强制浏览器提前加载首屏必需的资源(如主图、核心 JS)。

    html

    预览

    <!-- 预加载首屏 banner 图 -->
    <link rel="preload" href="banner.webp" as="image">
    <!-- 预加载核心 JS 模块 -->
    <link rel="preload" href="core.js" as="script">
    
2. 非关键资源延迟加载
  • JS 延迟执行:首屏不需要的交互逻辑(如弹窗、表单验证)延迟到 DOMContentLoaded 后执行。

    javascript

    运行

    // 非首屏 JS 延迟加载
    window.addEventListener('DOMContentLoaded', () => {
      import('./non-critical.js').then(module => {
        module.init();
      });
    });
    
  • 数据请求延迟:首屏不需要的数据(如用户历史记录、推荐列表)在首屏渲染完成后再请求。

3. 骨架屏(Skeleton Screen)

在首屏内容加载完成前,显示灰色占位骨架(模拟页面结构),减少用户等待感。

  • 实现方式

    • 简单场景:直接在 HTML 中写骨架屏的静态结构(避免额外请求)。
    • 复杂场景:用 Vue/React 组件生成骨架屏,打包时内联到 HTML。

    html

    预览

    <!-- 首屏骨架屏 -->
    <div class="skeleton">
      <div class="skeleton-header"></div>
      <div class="skeleton-banner"></div>
      <div class="skeleton-card"></div>
    </div>
    <!-- 真实内容(默认隐藏,加载完成后显示) -->
    <div class="content" style="display: none;">...</div>
    

四、网络与缓存:减少重复请求

1. CDN 加速

将静态资源(JS、CSS、图片)部署到 CDN(如 Cloudflare、阿里云 CDN),利用 CDN 节点的边缘缓存,减少用户到服务器的物理距离。

2. HTTP 缓存策略
  • 强缓存:对不常变更的资源(如第三方库、图片)设置 Cache-Control: max-age=31536000(1 年),浏览器直接从缓存读取,不发请求。
  • 协商缓存:对频繁变更的资源(如 HTML、业务 JS)设置 ETag 或 Last-Modified,服务器判断资源是否更新,未更新返回 304,避免重新下载。
3. HTTP/2 或 HTTP/3

启用多路复用(一个连接并发传输多个资源),减少 TCP 握手开销(需服务器支持,如 Nginx 配置 HTTP/2)。

4. 压缩传输
  • 开启 Gzip 或 Brotli 压缩(服务器层面配置,如 Nginx 的 gzip on),压缩 JS、CSS、HTML 等文本资源(通常可压缩 50% 以上)。
  • 图片、视频等二进制资源已压缩,无需再用 Gzip(反而可能变大)。

五、工程化与监控:持续优化

1. 构建工具优化
  • 用 Vite 替代 Webpack(开发环境快,但生产环境构建优化类似),通过 rollup 实现更高效的代码拆分和压缩。
  • 集成 webpack-bundle-analyzer 或 Vite 的 rollup-plugin-visualizer 分析包体积,定位大资源。
2. 预渲染(Prerendering)

对静态首屏(如营销页),在构建时生成完整的 HTML(而非客户端渲染),直接返回给浏览器,减少 JS 执行时间(工具:prerender-spa-plugin)。

3. 监控首屏性能
  • 用 Lighthouse 定期审计(Chrome 开发者工具内置),获取 FP、FCP、LCP 等指标。
  • 接入前端监控平台(如 Sentry、阿里云 ARMS),实时收集用户真实环境的加载性能数据,针对性优化。

总结:首屏加载优化 Checklist

  1. 内联关键 CSS,异步加载非关键 CSS/JS。
  2. 压缩并拆分 JS/CSS,移除冗余代码。
  3. 优化首屏图片(格式、压缩、裁剪、预加载)。
  4. 实现骨架屏,减少用户等待焦虑。
  5. 启用 CDN、HTTP 缓存、Gzip 压缩。
  6. 用代码拆分和懒加载减少首屏资源体积。
  7. 监控性能指标,持续迭代优化。

18、如何监测首屏加载时间

监测首屏加载时间需要结合浏览器原生 API性能监控工具真实用户数据(RUM) ,从开发阶段到生产环境全方位追踪关键指标。以下是具体方法和工具:

一、核心指标定义

首先明确首屏加载相关的关键性能指标(Web Vitals),避免混淆:

  • FP(First Paint) :首次绘制(页面第一个像素出现的时间)。
  • FCP(First Contentful Paint) :首次内容绘制(文本、图片等有意义内容出现的时间)。
  • LCP(Largest Contentful Paint) :最大内容绘制(首屏中最大元素加载完成的时间,核心指标,目标 < 2.5s)。
  • TTI(Time to Interactive) :可交互时间(页面完全响应用户操作的时间)。

其中,LCP 最能代表首屏加载完成的时间,因为它对应首屏中视觉权重最大的元素(如主图、标题)加载完成的时刻。

二、开发阶段:浏览器工具监测

在本地开发或测试环境,可直接用浏览器自带工具快速查看首屏性能:

1. Chrome 开发者工具(Performance 面板)
  • 打开 Chrome → F12 打开开发者工具 → 切换到 Performance 面板。

  • 点击 录制按钮(圆形按钮)  后刷新页面,录制完成后会生成性能时序图。

  • 在 Timings 栏中可直接看到 FP、FCP、LCP 等指标的具体时间:

    • 蓝色竖线:FP(首次绘制)
    • 绿色竖线:FCP(首次内容绘制)
    • 紫色竖线:LCP(最大内容绘制)
2. Lighthouse 审计工具
  • 开发者工具 → Lighthouse 面板 → 勾选 Performance → 点击 Generate report
  • 报告中会详细列出 LCP、FCP、TTI 等指标的得分和具体时间,并给出优化建议。

三、代码层面:通过 API 手动监测

在代码中嵌入监测逻辑,获取精确的首屏加载时间(可用于上报到监控系统)。

1. 监测 LCP(推荐,最接近首屏完成时间)

使用浏览器原生的 Largest Contentful Paint API 监听最大内容元素加载完成的时间:

javascript

运行

// 监测 LCP
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    // entry.startTime 即为 LCP 时间(单位:ms)
    console.log('LCP 时间:', entry.startTime);
    // 可在此处将数据上报到后端
    reportToServer('lcp', entry.startTime);
  }
}).observe({ type: 'largest-contentful-paint', buffered: true });
2. 监测 FCP 和 FP

使用 Performance Paint Timing API 监测首次绘制和首次内容绘制:

javascript

运行

// 监测 FP 和 FCP
new PerformanceObserver((entryList) => {
  const entries = entryList.getEntries();
  entries.forEach(entry => {
    console.log(`${entry.name} 时间:`, entry.startTime); // entry.name 为 'first-paint' 或 'first-contentful-paint'
    reportToServer(entry.name, entry.startTime);
  });
}).observe({ type: 'paint', buffered: true });
3. 自定义首屏判断(适用于复杂场景)

如果页面结构特殊(如动态渲染首屏),可手动标记首屏完成时间:

javascript

运行

// 示例:当首屏关键元素(如 id="main-content")加载完成时
const mainContent = document.getElementById('main-content');
if (mainContent) {
  // 监听元素加载完成(图片等资源需等 load 事件)
  mainContent.addEventListener('load', () => {
    const firstScreenTime = performance.now(); // 相对于页面加载开始的时间(ms)
    console.log('自定义首屏时间:', firstScreenTime);
    reportToServer('custom-first-screen', firstScreenTime);
  });
}

四、生产环境:真实用户监控(RUM)

通过埋点收集真实用户的首屏加载数据,反映实际体验(不同设备、网络环境差异大)。

1. 自建监控系统

将代码中监测到的 LCP、FCP 等指标通过接口上报到后端,存储到数据库(如 MySQL、InfluxDB),再通过可视化工具(如 Grafana)展示:

javascript

运行

// 上报函数示例
function reportToServer(metric, value) {
  const data = {
    metric, // 指标名称:lcp、fcp 等
    value,  // 时间(ms)
    url: window.location.href,
    userAgent: navigator.userAgent,
    timestamp: Date.now()
  };
  // 用 beacon API 确保页面卸载时也能上报
  navigator.sendBeacon('/api/performance', JSON.stringify(data));
}
2. 第三方监控工具

无需自建,直接集成成熟工具:

  • Sentry:支持前端性能监控,自动采集 LCP、FCP 等指标。
  • 阿里云 ARMS:实时监测首屏加载时间,按地区、设备分类统计。
  • Google Analytics(GA4) :通过 eventTiming 事件追踪性能指标。
  • Datadog:全链路监控,结合后端性能分析首屏慢的原因。

五、关键注意事项

  1. 区分 “实验室数据” 和 “真实用户数据”

    • 实验室数据(如 Lighthouse):在固定环境(如高速网络、高性能设备)下测试,用于优化参考。
    • 真实用户数据(RUM):反映不同用户的实际体验,需重点关注低性能设备和弱网环境。
  2. 排除异常值

    • 过滤掉极端数据(如加载时间 > 30s,可能是用户网络中断导致),避免影响统计结果。
  3. 结合网络和资源指标

    • 首屏慢可能是资源加载慢(如大图片)或网络延迟(如 DNS 解析、TCP 握手)导致,可通过 performance.getEntriesByType('resource') 分析具体资源的加载时间:

      javascript

      运行

      // 查看所有资源的加载时间
      performance.getEntriesByType('resource').forEach(resource => {
        console.log(`${resource.name} 加载时间:`, resource.duration);
      });
      

总结

  • 开发阶段用 Chrome Performance 面板 和 Lighthouse 快速定位问题。
  • 代码中通过 LCP API 精确监测首屏完成时间,并上报关键指标。
  • 生产环境用 RUM 工具 收集真实用户数据,持续优化不同场景下的首屏体验。